Notes on studying JavaFX
OS Windows 10
Java
>java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
Scene Builder JavaFX Scene Builder 8.4.1
The one developed by Gluon
JavaFX uses ** JavaFX Properties **, which is an extension of JavaBeans, as a way to represent the properties of an object.
JavaFX properties implement object properties as follows:
package sample.javafx.property;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public class MyClass {
private DoubleProperty value = new SimpleDoubleProperty();
public final double getValue() {
return this.value.get();
}
public final void setValue(double value) {
this.value.set(value);
}
public DoubleProperty valueProperty() {
return this.value;
}
}
value
) with private
--The property type is not a primitive type, but a wrapper type provided by JavaFX, such as DoubleProperty
.
--The wrapper type is prepared under the javafx.beans.property package. Are
--DoubleProperty
itself is an interface, and SimpleDoubleProperty
is provided as an implementation class.final
property name Property
--This returns the wrapper type as isMany of the classes provided by JavaFX implement this JavaFX property.
For example, the methods corresponding to the text
property of the Label class are listed below. (The actual definition is the Labeled class).
Observable JavaFX properties do more than just determine method declarations, they provide some special features that JavaBeans doesn't have. One of them is Observable.
Classes that implement ʻObservable` provide the ability to monitor for invalid content.
The DoubleProperty
used in the JavaFX property above also inherits from ʻObservable`.
This ʻObservable has a method called ʻaddListener (InvalidationListener)
that allows you to register a listener to be notified when the content becomes invalid.
package sample.javafx.property;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.SimpleDoubleProperty;
public class Main {
public static void main(String[] args) {
DoubleProperty a = new SimpleDoubleProperty();
a.addListener(observable -> {
System.out.println("observable=" + observable);
});
a.set(1.0);
a.set(2.0);
}
}
Execution result
observable=DoubleProperty [value: 1.0]
observable=DoubleProperty [value: 2.0]
The modified ʻObservable` itself is passed to the listener as an argument.
Some classes that implement ʻObservable` do not calculate immediately when a change is requested (for example, when the Setter method is called), and only when the content is requested next time. There is something like (delay calculation).
In other words, there may be a period in which the content is not finalized between the time when the content change is requested and the time when the content is actually calculated.
The fact that this content is not finalized is expressed as ** invalid **. Then, when the calculation is actually performed and the content is confirmed, it is expressed as ** change **.
ʻInvalidationListener` is now called back when the content becomes invalid.
Depending on the implementation class, the content is calculated immediately, so in that case, invalidation and change will occur at the same time (after invalidation, the content will be confirmed (changed) immediately).
ObservableValue
package sample.javafx.property;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.SimpleDoubleProperty;
public class Main {
public static void main(String[] args) {
DoubleProperty a = new SimpleDoubleProperty();
a.addListener((observableValue, oldValue, newValue) -> {
System.out.println("observableValue=" + observableValue + ", oldValue=" + oldValue + ", newValue=" + newValue);
});
a.set(1.0);
a.set(2.0);
}
}
Execution result
observableValue=DoubleProperty [value: 1.0], oldValue=0.0, newValue=1.0
observableValue=DoubleProperty [value: 2.0], oldValue=1.0, newValue=2.0
ʻObservableValue](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/beans/value/ObservableValue.html) interface exists as an interface that inherits Observable. It provides a method called ʻaddListener (ChangeListener)
, which allows you to register a listener to be notified when a value changes.
The listener is passed the changed ʻObservableValue` itself and the raw values before and after the change.
The property classes provided by JavaFX, including DoubleProperty
, implement ʻObservableValue, so both ʻInvalidationListener
and ChangeListener
listeners can be registered.
Binding ʻThere is Binding as an interface that inherits ObservableValue`.
Binding
represents the calculation result of aggregating multiple values.
The aggregated values are monitored by Binding
so that the results are automatically recalculated when the values change.
package sample.javafx.property;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public class Main {
public static void main(String[] args) {
DoubleProperty a = new SimpleDoubleProperty(1.0);
DoubleProperty b = new SimpleDoubleProperty(2.0);
DoubleBinding sum = a.add(b);
System.out.println("sum=" + sum.get());
a.set(3.0);
System.out.println("sum=" + sum.get());
}
}
Execution result
sum=3.0
sum=5.0
In this example, we are creating a DoubleBinding
that represents the sum of two DoubleProperty
s.
If you change the value of one of the DoubleProperty
and get the value of DoubleBinding
again, the sum is the recalculated value.
In this way, Binding
provides a mechanism to combine multiple values, monitor changes in each value, and automatically update the calculation results.
The values collected by Binding
are called ** source ** or ** dependency **.
The Binding
itself does not limit the type of the source, but in reality it is usually ʻObservable or ʻObservableValue
.
package sample.javafx.property;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public class Main {
public static void main(String[] args) {
DoubleProperty a = new SimpleDoubleProperty(1.0);
DoubleProperty b = new SimpleDoubleProperty(2.0);
DoubleBinding sum = a.add(b);
System.out.println("sum=" + sum);
System.out.println("sum.get()=" + sum.get());
System.out.println("sum=" + sum);
a.set(3.0);
System.out.println("sum=" + sum);
System.out.println("sum.get()=" + sum.get());
System.out.println("sum=" + sum);
}
}
Execution result
sum=DoubleBinding [invalid]
sum.get()=3.0
sum=DoubleBinding [value: 3.0]
sum=DoubleBinding [invalid]
sum.get()=5.0
sum=DoubleBinding [value: 5.0]
--All classes that implement Binding
that exist in the standard library are delayed in calculation.
--In other words, it does not recalculate immediately after the source value is changed, but recalculates when an attempt is made to obtain the calculation result.
--Binding
is invalid until recalculation is done
package sample.javafx.property;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public class Main {
public static void main(String[] args) {
DoubleProperty a = new SimpleDoubleProperty(1.0);
DoubleProperty b = new SimpleDoubleProperty(2.0);
DoubleBinding sum = a.add(b);
sum.addListener((observableValue, oldValue, newValue) -> {
System.out.println("Changed(oldValue=" + oldValue + ", newValue=" + newValue + ")");
});
System.out.println("sum=" + sum);
System.out.println("sum.get()=" + sum.get());
System.out.println("sum=" + sum);
a.set(3.0);
System.out.println("sum=" + sum);
System.out.println("sum.get()=" + sum.get());
System.out.println("sum=" + sum);
}
}
Execution result
sum=DoubleBinding [value: 3.0]
sum.get()=3.0
sum=DoubleBinding [value: 3.0]
Changed(oldValue=3.0, newValue=5.0)
sum=DoubleBinding [value: 5.0]
sum.get()=5.0
sum=DoubleBinding [value: 5.0]
--If a change notification listener is registered with ʻObservableValue.addListener (ChangeListener) , the delay calculation will not be performed. --In other words, if you have registered
ChangeListener, it will always be calculated immediately. --This is not limited to
Binding, but is common to all implementations of delay calculation that implement ʻObservableValue
.
--For more information, see Observable Javadoc and [Official Tutorial](https: // See the description in docs.oracle.com/javase/jp/8/javafx/properties-binding-tutorial/binding.htm#sthref12)
There are two types of APIs for creating Binding
: ** high-level API ** and ** low-level API **.
The higher level API is divided into ** Fluent API ** and ** Bindings class factory method **.
Fluent API
package sample.javafx.property;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public class Main {
public static void main(String[] args) {
DoubleProperty a = new SimpleDoubleProperty(2.0);
DoubleBinding binding = a.add(3).multiply(2).subtract(4.0).divide(3.0);
System.out.println("((2.0 + 3) * 2 - 4.0) / 3.0 = " + binding.get());
a.set(5.0);
System.out.println("((5.0 + 3) * 2 - 4.0) / 3.0 = " + binding.get());
}
}
Execution result
((2.0 + 3) * 2 - 4.0) / 3.0 = 2.0
((4.0 + 3) * 2 - 4.0) / 3.0 = 4.0
--The Fluent API allows you to build Binding
with a fluent interface.
--ʻA.add (3) .multiply (2) .subtract (4.0) .divide (3.0) part --These methods are defined in the interface [NumberExpression](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/beans/binding/NumberExpression.html) --The class used in the property implements this interface --In addition to numerical operations, there are also methods for performing comparison operations and logical operations. --The return type of the Fluent API of the numeric operation system of
NumberExpression is
NumberBinding, but since
NumberBinding is also a subinterface of
NumberExpression`, call Fluent API as it is. Be able to
--The relationship between each interface and class is as shown in the figure below (only a part).
package sample.javafx.property;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public class Main {
public static void main(String[] args) {
DoubleProperty a = new SimpleDoubleProperty(2.0);
DoubleBinding binding =
Bindings.divide(
Bindings.subtract(
Bindings.multiply(
Bindings.add(a, 3)
,2
)
,4.0
)
,3.0
);
System.out.println("((2.0 + 3) * 2 - 4.0) / 3.0 = " + binding.get());
a.set(5.0);
System.out.println("((5.0 + 3) * 2 - 4.0) / 3.0 = " + binding.get());
}
}
Execution result
((2.0 + 3) * 2 - 4.0) / 3.0 = 2.0
((5.0 + 3) * 2 - 4.0) / 3.0 = 4.0
--The class Bindings has a large number of Binding
factory methods. Has been
--Some exist in the Fluent API, others do not
――There are a lot of them for the time being, so it's a good idea to take a quick look and know what's going on.
-when () so
MyBinding.java
package sample.javafx.property;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
public class MyBinding extends DoubleBinding {
private final DoubleProperty source;
public MyBinding(DoubleProperty source) {
this.bind(source);
this.source = source;
}
@Override
protected double computeValue() {
return ((this.source.get() + 3) * 2 - 4.0) / 3.0;
}
}
package sample.javafx.property;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public class Main {
public static void main(String[] args) {
DoubleProperty a = new SimpleDoubleProperty(2.0);
DoubleBinding binding = new MyBinding(a);
System.out.println("((2.0 + 3) * 2 - 4.0) / 3.0 = " + binding.get());
a.set(5.0);
System.out.println("((5.0 + 3) * 2 - 4.0) / 3.0 = " + binding.get());
}
}
Execution result
((2.0 + 3) * 2 - 4.0) / 3.0 = 2.0
((5.0 + 3) * 2 - 4.0) / 3.0 = 4.0
--The low-level API inherits from the Binding
abstract class and creates its ownBinding
class.
--Here, we are creating MyBinding
that inherits DoubleBinding
.
--Register the monitored ʻObservablewith the
bind () method that exists in the parent class. --The arguments are variable length, so you can monitor multiple ʻObservable
s.
--When the contents of ʻObservableregistered with
bind ()are changed,
computeValue ()` is called, so implement it so that the calculation result is returned.
package sample.javafx.property;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public class Main {
public static void main(String[] args) {
DoubleProperty a = new SimpleDoubleProperty();
DoubleProperty b = new SimpleDoubleProperty();
System.out.println("a=" + a + ", b=" + b);
a.bind(b);
System.out.println("a=" + a + ", b=" + b);
b.set(3.0);
System.out.println("a=" + a + ", b=" + b);
System.out.println("a=" + a.get() + ", b=" + b);
}
}
Execution result
a=DoubleProperty [value: 0.0], b=DoubleProperty [value: 0.0]
a=DoubleProperty [bound, invalid], b=DoubleProperty [value: 0.0]
a=DoubleProperty [bound, invalid], b=DoubleProperty [value: 3.0]
a=3.0, b=DoubleProperty [value: 3.0]
--If you use the bind (ObservalbeValue)
method defined in Property , One-way binding can be realized
--It now monitors the value of ʻObservableValuepassed as an argument, and when the value changes, it changes its own value to the same value as the monitored target. --If you want to link the value of one property with another, you can use this
bind ()` to implement it simply.
MyClass.java
package sample.javafx.property;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
public class MyClass {
private ReadOnlyDoubleWrapper value = new ReadOnlyDoubleWrapper();
public final double getValue() {
return this.value.getValue();
}
public final ReadOnlyDoubleProperty valueProperty() {
return this.value.getReadOnlyProperty();
}
public void updateValue() {
this.value.set(99.9);
}
}
Main.java
package sample.javafx.property;
public class Main {
public static void main(String[] args) {
MyClass myClass = new MyClass();
System.out.println(myClass.valueProperty());
myClass.updateValue();
System.out.println(myClass.valueProperty());
}
}
Execution result
ReadOnlyDoubleProperty [value: 0.0]
ReadOnlyDoubleProperty [value: 99.9]
--If you publish DoubleProperty
as it is, you can freely rewrite the value at the publishing destination.
--If you want to prevent the value from being changed at the publishing destination, do not publish setter and set the type of the property used internally to ReadOnly ** Wrapper
type.
--The ReadOnly ** Wrapper
type can change its value normally, but the object obtained bygetReadOnlyProperty ()
is read-only.
package sample.javafx;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private TextField textField;
private StringProperty value = new SimpleStringProperty();
@Override
public void initialize(URL location, ResourceBundle resources) {
this.textField.textProperty().bindBidirectional(this.value);
}
@FXML
public void checkValue() {
System.out.println("value=" + value.getValue());
}
@FXML
public void resetValue() {
this.value.set("reset!!");
}
}
Execution result
-BindBidirectional () defined in Property .oracle.com/javase/jp/8/javafx/api/javafx/beans/property/Property.html#bindBidirectional-javafx.beans.property.Property-) When you use the method, you can make two Property
bidirectional. Can be bound
The bidirectional binding method provided for Property <T>
can only be bidirectionally bound to a property of the same Property <T>
type.
On the other hand, StringProperty has the following two methods for bidirectional binding. Has been added.
--Format specification --Converter designation
package sample.javafx;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.Format;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private TextField textField;
private DoubleProperty value = new SimpleDoubleProperty();
@Override
public void initialize(URL location, ResourceBundle resources) {
Format format = new DecimalFormat("#,##0.00");
this.textField.textProperty().bindBidirectional(this.value, format);
}
@FXML
public void checkValue() {
System.out.println("value=" + value.getValue());
}
@FXML
public void resetValue() {
this.value.set(9876.54);
}
}
Execution result
--You can specify Format
as the second argument
--The StringProperty
side will be set to a string formatted with Format
, and the other property will be set to the parsed result.
--WARINING level log is output with stack trace if there is malformed input
package sample.javafx;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import javafx.util.StringConverter;
import java.net.URL;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private TextField textField;
private ObjectProperty<LocalDate> value = new SimpleObjectProperty<>(LocalDate.of(2017, 1, 1));
@Override
public void initialize(URL location, ResourceBundle resources) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu/MM/dd");
this.textField.textProperty().bindBidirectional(this.value, new StringConverter<LocalDate>() {
@Override
public String toString(LocalDate date) {
if (date == null) {
return "";
}
try {
return formatter.format(date);
} catch (DateTimeException e) {
return "";
}
}
@Override
public LocalDate fromString(String text) {
try {
return LocalDate.parse(text, formatter);
} catch (DateTimeParseException e) {
return null;
}
}
});
}
@FXML
public void checkValue() {
System.out.println("value=" + value.getValue());
}
@FXML
public void resetValue() {
this.value.set(LocalDate.of(2017, 1, 1));
}
}
Execution result
-By specifying StringConverter, any value and StringProperty
are bidirectional. Can be bound to
--Two abstract methods are defined in StringConverter
--String toString (T)
: Convert object T
to String
--T fromString (String)
: Converts String
to object T
--For the time being, LocalDateStringConverter is prepared as standard.
――However, if you cannot parse or format it, the WARNING log including the stack trace will be output, so it seems a little difficult to use.
--If it is not free input, it may be usable
JavaFX provides its own collection classes that extend the standard collections (List
and Map
).
package sample.javafx.property;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
public class Main {
public static void main(String[] args) {
ObservableList<String> list = FXCollections.observableArrayList("fizz", "buzz");
list.addListener((Change<? extends String> change) -> {
System.out.println("list changed change=" + change);
});
list.add("foo");
list.remove("buzz");
FXCollections.sort(list);
}
}
Execution result
list changed change={ [foo] added at 2 }
list changed change={ [buzz] removed at 1 }
list changed change={ permutated by [0, 1] }
--ObservableList is an interface that inherits java.util.List
and has a monitoring function. Has been added to realize
--Instantiation uses the factory method of FXCollections
--FXCollections
is a utility class for JavaFX collections that has methods similar to java.util.Collections
.
--ʻAddListener (ListChangeListener) can register listeners to be notified of changes --ʻObservableList
also inherits ʻObservable --And ʻObservable
also defines a method with the same name ʻaddListener (InvalidationListener)but different arguments. --The listeners that both receive as arguments are both functional interfaces and have only one method argument. --In other words, if you write the simplest lambda expression with the argument types omitted, the compiler will not be able to determine which ʻaddListener ()
is being used, resulting in a compilation error.
--There is no choice, so even when writing a lambda expression, you must write at least the argument type without omitting it.
When the list changes, the listener is passed a Change object.
The Change
object contains information about how the list was changed.
package sample.javafx.property;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
public class Main {
public static void main(String[] args) {
ObservableList<String> list = FXCollections.observableArrayList("one", "two", "three");
list.addListener((Change<? extends String> change) -> {
System.out.println("==========================================");
while (change.next()) {
System.out.println("list=" + change.getList());
if (change.wasAdded()) {
System.out.println("add to: " + change);
}
if (change.wasRemoved()) {
System.out.println("Delete: " + change);
}
if (change.wasPermutated()) {
System.out.println("Change order: " + change);
}
if (change.wasReplaced()) {
System.out.println("Replacement: " + change);
}
}
});
list.add("FOO");
list.remove("one");
FXCollections.sort(list);
list.set(1, "hoge");
}
}
Execution result
==========================================
list=[one, two, three, FOO]
add to: { [FOO] added at 3 }
==========================================
list=[two, three, FOO]
Delete: { [one] removed at 0 }
==========================================
list=[FOO, three, two]
Change order: { permutated by [2, 1, 0] }
==========================================
list=[FOO, hoge, two]
add to: { [three] replaced by [hoge] at 1 }
Delete: { [three] replaced by [hoge] at 1 }
Replacement: { [three] replaced by [hoge] at 1 }
--You must ** always execute the next ()
method before you can get the changes from the Change
object **
--When trying to get changes without calling next ()
, ʻIllegalStateExceptionis thrown --If there are multiple changes in one notification, you can move to the next change by calling
next (). --If you use
removeAll ()described later, multiple changes may be notified at once, so if you turn it with
while, you can check all the changes. --There are types of changes such as "add" and "delete" --You can see what kind of changes have occurred with the
was *** ()methods such as
wasAdded ()and
wasRemoved (). --There are the following notes on the listener implementation -**
Change` object must not be used in another thread **
-** Do not change the state of the list in the listener **
-It is described in Javadoc of Change object.
package sample.javafx.property;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
ObservableList<String> list = FXCollections.observableArrayList("one", "two", "three");
list.addListener((Change<? extends String> change) -> {
System.out.println("==========================================");
while (change.next()) {
System.out.println("list=" + change.getList());
System.out.println("added=" + change.wasAdded());
System.out.println("from=" + change.getFrom());
System.out.println("to=" + change.getTo());
System.out.println("addedSubList=" + change.getAddedSubList());
System.out.println("addedSize=" + change.getAddedSize());
}
});
list.add("FOO");
list.addAll(Arrays.asList("hoge", "fuga"));
}
}
Execution result
==========================================
list=[one, two, three, FOO]
added=true
from=3
to=4
addedSubList=[FOO]
addedSize=1
==========================================
list=[one, two, three, FOO, hoge, fuga]
added=true
from=4
to=6
addedSubList=[hoge, fuga]
addedSize=2
--When an element is added, wasAdded ()
returns true
--You can get an index pointing to where the element was added with getFrom ()
, getTo ()
--getFrom ()
is the first index (including this index) to which the element was added.
--getTo ()
is the last index to which the element was added (this index is not included)
--You can get a List
containing only the added elements withgetAddedSubList ()
--You can get the number of added elements with getAddedSize ()
package sample.javafx.property;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
public class Main {
public static void main(String[] args) {
ObservableList<String> list
= FXCollections.observableArrayList(
"one", "two", "three", "four", "five", "six", "seven");
list.addListener((Change<? extends String> change) -> {
System.out.println("==========================================");
System.out.println("list=" + change.getList());
while (change.next()) {
System.out.println("----------------------------------");
System.out.println("removed=" + change.wasRemoved());
System.out.println("from=" + change.getFrom());
System.out.println("to=" + change.getTo());
System.out.println("removed=" + change.getRemoved());
System.out.println("removedSize=" + change.getRemovedSize());
}
});
list.remove("three");
list.removeAll("two", "seven");
list.retainAll("one", "six");
}
}
Execution result
==========================================
list=[one, two, four, five, six, seven]
----------------------------------
removed=true
from=2
to=2
removed=[three]
removedSize=1
==========================================
list=[one, four, five, six]
----------------------------------
removed=true
from=1
to=1
removed=[two]
removedSize=1
----------------------------------
removed=true
from=4
to=4
removed=[seven]
removedSize=1
==========================================
list=[one, six]
----------------------------------
removed=true
from=1
to=1
removed=[four, five]
removedSize=2
--wasRemoved ()
returns true
when the element is removed
--In case of deletion, getFrom ()
returns the ** starting index ** of the deleted element in the original List
.
--Also, getTo ()
returns the same value asgetFrom ()
(although I'm a little skeptical about the Javadoc description).
--If you look closely at the Javadoc for Change
, it seems like you can't readgetFrom ()
andgetTo ()
at the time of deletion, but what about (actually unconfirmed)? ?)
--You can get the removed elements with List
withgetRemoved ()
--getRemovedSize ()
returns the number of removed elements
package sample.javafx.property;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
public class Main {
public static void main(String[] args) {
ObservableList<String> list
= FXCollections.observableArrayList("aaa", "ccc", "bbb", "eee", "ddd", "fff");
list.addListener((Change<? extends String> change) -> {
System.out.println("==========================================");
System.out.println("new list=" + change.getList());
while (change.next()) {
System.out.println("permutated=" + change.wasPermutated());
for (int oldIndex=change.getFrom(); oldIndex<change.getTo(); oldIndex++) {
int newIndex = change.getPermutation(oldIndex);
System.out.println("old(" + oldIndex + ") -> new(" + newIndex + ")");
}
}
});
System.out.println("old list=" + list);
list.sort(String::compareTo);
}
}
Execution result
old list=[aaa, ccc, bbb, eee, ddd, fff]
==========================================
new list=[aaa, bbb, ccc, ddd, eee, fff]
permutated=true
old(0) -> new(0)
old(1) -> new(2)
old(2) -> new(1)
old(3) -> new(4)
old(4) -> new(3)
old(5) -> new(5)
--wasPermutated ()
returns true
when the list is reordered
--If you pass the index before the change to getPermutated (int)
, the index after the change is returned.
package sample.javafx.property;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
public class Main {
public static void main(String[] args) {
ObservableList<String> list = FXCollections.observableArrayList("one", "two", "three");
list.addListener((Change<? extends String> change) -> {
System.out.println("==========================================");
while (change.next()) {
System.out.println("list=" + change.getList());
System.out.println("replaced=" + change.wasReplaced());
System.out.println("added=" + change.wasAdded());
System.out.println("removed=" + change.wasRemoved());
System.out.println("from=" + change.getFrom());
System.out.println("to=" + change.getTo());
System.out.println("addedSubList=" + change.getAddedSubList());
System.out.println("addedSize=" + change.getAddedSize());
System.out.println("removed=" + change.getRemoved());
System.out.println("removedSize=" + change.getRemovedSize());
}
});
list.set(1, "FOO");
}
}
Execution result
==========================================
list=[one, FOO, three]
replaced=true
added=true
removed=true
from=1
to=2
addedSubList=[FOO]
addedSize=1
removed=[two]
removedSize=1
--wasReplaced ()
returns true
when an element is replaced by another element
--Strictly speaking, wasReplaced ()
becomes true
when wasAddedd () && wasRemoved ()
--That is, only wasReplaced ()
can never be true
--When wasReplaced ()
is true
, wasAddedd ()
and wasRemoved ()
are always true
.
package sample.javafx.property;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import java.util.Comparator;
public class Main {
public static void main(String[] args) {
ObservableList<String> list = FXCollections.observableArrayList("one", "two", "three");
SortedList<String> sortedList = list.sorted();
System.out.println("list=" + list);
System.out.println("sortedList=" + sortedList);
list.addAll("four", "five", "six");
System.out.println("list=" + list);
System.out.println("sortedList=" + sortedList);
sortedList.setComparator(Comparator.reverseOrder());
System.out.println("list=" + list);
System.out.println("sortedList=" + sortedList);
}
}
Execution result
list=[one, two, three]
sortedList=[one, three, two]
list=[one, two, three, four, five, six]
sortedList=[five, four, one, six, three, two]
list=[one, two, three, four, five, six]
sortedList=[two, three, six, one, four, five]
--You can get the sorted list (SortedList
) linked with the original ʻObservableList by executing the
sorted () method. --If you change the original ʻObservalbeList
, the state of SortedList
will also be updated in a sorted state.
--SortedList
implements ʻObservableList --There is also a
sorted (Comparator)method that allows you to pass a
Comparatoras an argument --You can also change the sorting method later with
setComparator (Comparator)`
package sample.javafx.property;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
public class Main {
public static void main(String[] args) {
ObservableList<String> list = FXCollections.observableArrayList("one", "two", "three");
FilteredList<String> filteredList = list.filtered(e -> e.contains("e"));
System.out.println("list=" + list);
System.out.println("filteredList=" + filteredList);
list.addAll("four", "five");
System.out.println("list=" + list);
System.out.println("filteredList=" + filteredList);
filteredList.setPredicate(e -> e.contains("f"));
System.out.println("list=" + list);
System.out.println("filteredList=" + filteredList);
}
}
Execution result
list=[one, two, three]
filteredList=[one, three]
list=[one, two, three, four, five]
filteredList=[one, three, five]
list=[one, two, three, four, five]
filteredList=[four, five]
--You can get the filtered list (FilteredList
) linked with the original ʻObservableList by executing the
filtered (Predicate) method. --If you change the original ʻObservalbeList
, the state of FilteredList
will also be updated in a filtered state.
--You can also change the filtering conditions later with setPredicate ()
--FilteredList
implements ʻObservableList`
package sample.javafx.property;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener.Change;
import javafx.collections.ObservableMap;
public class Main {
public static void main(String[] args) {
ObservableMap<String, String> map = FXCollections.observableHashMap();
map.put("foo", "FOO");
map.put("bar", "BAR");
map.addListener((Change<? extends String, ? extends String> change) -> {
System.out.println("==============================");
System.out.println("map=" + change.getMap());
System.out.println("key=" + change.getKey());
System.out.println("added=" + change.wasAdded());
System.out.println("valueAdded=" + change.getValueAdded());
System.out.println("removed=" + change.wasRemoved());
System.out.println("valueRemoved=" + change.getValueRemoved());
});
map.put("fizz", "FIZZ");
map.put("bar", "BARBAR");
map.remove("foo");
}
}
Execution result
==============================
map={bar=BAR, foo=FOO, fizz=FIZZ}
key=fizz
added=true
valueAdded=FIZZ
removed=false
valueRemoved=null
==============================
map={bar=BARBAR, foo=FOO, fizz=FIZZ}
key=bar
added=true
valueAdded=BARBAR
removed=true
valueRemoved=BAR
==============================
map={bar=BARBAR, fizz=FIZZ}
key=foo
added=false
valueAdded=null
removed=true
valueRemoved=FOO
--ʻObservableMapis provided as a monitorable
Map --Use
FXCollections.observableHashMap () to create an instance --
Change in
Mapis not as complicated as
List --Unlike
List,
next () is unnecessary --There are two types of changes: "added (
wasAdded () )" and "deleted (
wasRemoved () )". --The value added by
getValueAdded (), You can get the removed value with
getValueRemoved ()`
--If you set a different value for an existing key, the "add" and "delete" changes will occur at the same time.
Folder structure
`-src/main/
|-java/sample/javafx/
| |-Main.java
| `-MainController.java
|
`-resources/
|-main.fxml
`-my-style.css
main.fxml
my-style.css
.label {
-fx-background-color: skyblue;
}
Main.java
package sample.javafx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.net.URL;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
URL url = this.getClass().getResource("/main.fxml");
FXMLLoader loader = new FXMLLoader(url);
Parent root = loader.load();
Scene scene = new Scene(root);
scene.getStylesheets().add("my-style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
}
Execution result
Main.java
Scene scene = new Scene(root);
scene.getStylesheets().add("my-style.css");
--Stylesheet files can be added by using Scene.getStylesheets ()
to get a list of stylesheet URLs and ʻadd () to it. --You can register multiple style sheets to be used in this scene. --URL is described in the format
[scheme:] [// authority] [path] --If
[scheme:]is omitted, only
[path] is considered. --
[path]` is treated as ** relative to the root of the classpath **
--For details, refer to Javadoc of Scene.getStylesheets ().
my-style.css
.label {
-fx-background-color: skyblue;
}
--The way to write CSS is basically the same as the CSS used in HTML.
--Selector {property: value;}
--However, property names almost always start with the prefix -fx
.
--If you set .xxxx
in HTML CSS, it matches the tag withclass = "xxxx"
set.
--On the other hand, for JavaFX CSS, .xxxx
matchesstyleClass = "xxxx"
--Some nodes (Label
, ListView
, etc.) have the styleClass
attribute named according to certain conversion rules by default.
--For example, the Label
class defaults to label
styleClass
, and the ListView
class defaults to list-view`` styleClass
.
--The conversion rule is simple, each word in the class name is separated by -
and all are in lowercase.
――However, this conversion does not have any automatic conversion logic, it is just set in each class according to the above rules.
--What that means is that you can see it by looking at the implementation of the Label
class.
Label.java
...
public class Label extends Labeled {
public Label() {
initialize();
}
...
private void initialize() {
getStyleClass().setAll("label");
...
}
--ʻInitialzie () is called in the constructor, and the string
labelis passed to the
styleClass property (it does not mean that
styleClass, which is automatically converted from the class name, is set). --So, the instance of the
Label class has a
styleClassof
label by default, which is specified in the CSS file using a selector using
. (
. .label`)
--Click here to see what default class is set for which node [https://docs.oracle.com/javase/jp/8/javafx/api/javafx/scene/doc-files/cssref.html #nodes)
--"Style class: empty by default"
Where I write "style class: button"
my-style.css
.my-class {
-fx-background-color: pink;
-fx-padding: 10px;
-fx-border-color: black;
-fx-border-width: 1px;
-fx-border-style: solid;
-fx-underline: true;
}
Execution result
--At first glance, properties specified by CSS (such as -fx-background-color
and -fx-padding
) appear to have -fx
at the beginning of the properties that can be specified by HTML CSS.
--So, if you're not sure which properties you can use, you can add -fx
to the properties used in HTML CSS.
--But there are actually some properties that don't exist in HTML CSS, like -fx-underline: true;
--HTML CSS text-decoration: underline;
--This actually refers to the properties of the target node
--In the sample, CSS is specified for the Label
class, but ʻunderline is the [underline property](https://docs.oracle.com) of the
Labeledclass, which is the parent class of the
Labelclass. /javase/jp/8/javafx/api/javafx/scene/control/Labeled.html#fontProperty) is supposed to be specified. --However, if the properties of the node and the properties specified by CSS always match exactly, that is not the case. --For example,
border and
backgroundare divided into small properties such as
-fx-border-style and
-fx-background-colorinstead of
-fx-border and
-fx-background`. Have been
--For information on which properties can be specified for which node, see [Official Documents](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/scene/doc-files/cssref] .html) explained in detail
main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.text.Font?>
<Label fx:id="label" alignment="CENTER" prefHeight="200.0" prefWidth="300.0"
styleClass="my-class"
text="CSS Test" xmlns="http://javafx.com/javafx/8.0.151" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.javafx.MainController">
<font>
<Font size="30.0" />
</font>
</Label>
my-style.css
.my-class {
-fx-font-weight: bold;
-fx-underline: true;
-fx-text-fill: red;
}
Execution result
--Scene Builder's Style Class
allows you to set arbitrary styleClass
attributes for elements
MainContoller.java
package sample.javafx;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private Label label;
@Override
public void initialize(URL location, ResourceBundle resources) {
ObservableList<String> styleClass = label.getStyleClass();
styleClass.add("my-class");
System.out.println("styleClass=" + styleClass);
}
}
--Get ʻObservableList of
styleClasswith
getStyleClass ()from the node --Any attribute can be added to the list by ʻadd ()
Folder structure
|-my-styles.css
`-src/main/java/
`-sample/javafx/
|-Main.java
:
my-style.css
.my-class {
-fx-background-color: green;
}
Main.java
package sample.javafx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.net.URL;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
URL url = this.getClass().getResource("/main.fxml");
FXMLLoader loader = new FXMLLoader(url);
Parent root = loader.load();
Scene scene = new Scene(root);
scene.getStylesheets().add("file:./my-style.css"); //★ Specify a local file
primaryStage.setScene(scene);
primaryStage.show();
}
}
Execution result
--Specify file: [path]
to specify a local CSS file
--If you are reading with Path
or File
, you can get the ʻURIobject with the
toURI (),
toUri ()method and pass the
toString ()`.
How to set -fx-background-image
Folder structure
|-image.jpg
`-src/main/
|-resources/
| |-img/
| | `-image.jpg
| |-my-style.css
| `-main.fxml
:
my-style.css
.my-class {
-fx-background-image: url('/img/image.jpg');
}
--Specify with ʻurl --The format is the same as reading a CSS file, and if
schema` is omitted, the file will be under the classpath.
my-style.css
.my-class {
-fx-background-image: url('file:./image.jpg');
}
--When specifying a local file, use file: [path]
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private Pane bluePane;
@FXML
private Pane yellowPane;
@FXML
private Button button;
@FXML
private TextField textField;
@FXML
private Label label;
@Override
public void initialize(URL location, ResourceBundle resources) {
this.registerHandlers(this.bluePane, "bluePane");
this.registerHandlers(this.yellowPane, "yellowPane");
this.registerHandlers(this.button, "button");
this.registerHandlers(this.textField, "textField");
this.registerHandlers(this.label, "label");
}
private void registerHandlers(Node node, String name) {
this.registerEventFilter(node, name);
this.registerEventHandler(node, name);
}
private void registerEventFilter(Node node, String name) {
node.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> System.out.println("filter " + name));
}
private void registerEventHandler(Node node, String name) {
node.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("handler " + name));
}
}
Execution result
Click on the blue area
filter bluePane
handler bluePane
Click on the yellow area
filter bluePane
filter yellowPane
handler yellowPane
handler bluePane
Click the button
filter bluePane
filter yellowPane
filter button
handler button
Click on the text field
filter bluePane
filter yellowPane
filter textField
handler textField
Click on the label
filter bluePane
filter yellowPane
filter label
handler label
handler yellowPane
handler bluePane
When an event occurs, processing is executed in the following order
[^ 2]: In the official Japanese document, it is described as "root", but it is written as "route" because it seems to be mistaken for "root".
--First, the node (target) where the event occurred is determined. --For example, if it is a click event, the clicked node will be targeted, and if it is a key event, the focused node will be targeted.
――Next, follow the scene graph from the target toward the parent and determine the route to the root node. --The next event capture phase and event bubbling phase will be executed along this path.
Click on the text field
filter bluePane
filter yellowPane
filter textField
...
--In the event capture phase, the event filter registered in each node is executed from the root to the target on the determined route.
MainController.java
private void registerEventFilter(Node node, String name) {
node.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> System.out.println("filter " + name));
}
--Event filters can be registered with nodes with ʻaddEventFilter (EventType, EventHandler) `
Click on the label
...
handler label
handler yellowPane
handler bluePane
--In the event bubbling phase, conversely, the event handler registered in each node is executed from the target to the root node.
MainController.java
private void registerEventHandler(Node node, String name) {
node.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("handler " + name));
}
--Event handlers can be registered with ʻaddEventHandler (EventType, EventHandler) --For event handlers that are often used such as mouse clicks, there is a dedicated setting method, so you can use that as well. --
setOnMouseClicked (EventHandler) etc. --This event handler is also registered when event processing is registered with the ʻonMouseClicked
attribute of FXML.
MainController.java
package sample.javafx;
...
public class MainController implements Initializable {
...
@Override
public void initialize(URL location, ResourceBundle resources) {
this.registerHandlers(this.bluePane, "bluePane");
this.registerEventFilter(this.yellowPane, "yellowPane");
this.yellowPane.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
System.out.println("handler yellowPane");
e.consume();
});
this.registerHandlers(this.button, "button");
this.registerHandlers(this.textField, "textField");
this.registerHandlers(this.label, "label");
}
...
}
--The event handler in the area with a yellow background (yellowPane
) is executing theconsume ()
method of MouseEvent
.
Execution result
Click on the yellow area
filter bluePane
filter yellowPane
filter label
handler label
handler yellowPane
--You can break the chain of events by executing the ʻEvent.consume ()` method. --This is expressed as ** event consumption ** --If you consume an event during the event capture stage, event bubbling will no longer occur.
Click the button
filter bluePane
filter yellowPane
filter button
handler button
Click on the text field
filter bluePane
filter yellowPane
filter textField
handler textField
Click on the label
filter bluePane
filter yellowPane
filter label
handler label
handler yellowPane
handler bluePane
--Label
is performing event bubbling normally, but Button
and TextField
are interrupted.
--All event capture phases are running
For the time being, the official document says as follows.
The JavaFX UI control's default handler generally consumes most input events.
1 Event Processing (Release 8) | JavaFX: Event Processing
However, I'm not sure exactly where to check which class consumes the event.
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
public class MainController {
@FXML
public void onDragOver(DragEvent e) {
e.acceptTransferModes(TransferMode.ANY);
e.consume();
}
@FXML
public void onDragDropped(DragEvent e) {
Dragboard dragboard = e.getDragboard();
String string = dragboard.getString();
System.out.println("dropped string = " + string);
e.consume();
}
}
Execution result
Description
MainController.java
@FXML
public void onDragOver(DragEvent e) {
e.acceptTransferModes(TransferMode.ANY);
e.consume();
}
--Register a handler for the DRAG_OVER
event for the drop destination node
--So, by executing the DragEvent.acceptTransferModes ()
method, you can accept the drop.
--In the argument, specify which transfer mode (COPY
, MOVE
, or LINK
) is accepted by the constant of TransferMode
.
―― ʻANYrepresents all three transfer modes (that is, anything is OK). --If you do not accept the drop, the mouse cursor will become an icon indicating that it cannot be dropped. --Finally, execute the
consume ()method to consume the event ――It works even if you don't consume it, but the official documentation always mentions
consume (). --Maybe if the parent node also has a drag-and-drop process, it will not be executed by mistake. --This time, ʻacceptTransferModes ()
is always executed, but by controlling the call by the condition, you can control whether to allow the drop depending on what you are trying to drop.
MainController.java
@FXML
public void onDragDropped(DragEvent e) {
Dragboard dragboard = e.getDragboard();
String string = dragboard.getString();
System.out.println("dropped string = " + string);
e.consume();
}
--Register a handler in the DRAG_DROPPED
event of the target node for the process at the time of drop.
--You can access the drop contents from Dragboard
obtained byDragEvent.getDragboard ()
.
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import java.io.File;
import java.util.List;
public class MainController {
@FXML
public void onDragOver(DragEvent e) {
Dragboard dragboard = e.getDragboard();
if (dragboard.hasFiles()) {
e.acceptTransferModes(TransferMode.ANY);
}
e.consume();
}
@FXML
public void onDragDropped(DragEvent e) {
System.out.println("onDragDropped()");
Dragboard dragboard = e.getDragboard();
List<File> files = dragboard.getFiles();
files.forEach(file -> System.out.println(file.getName()));
e.consume();
}
}
Execution result
Description
MainController.java
@FXML
public void onDragOver(DragEvent e) {
Dragboard dragboard = e.getDragboard();
if (dragboard.hasFiles()) {
e.acceptTransferModes(TransferMode.ANY);
}
e.consume();
}
--You can get it with DragEvent.getDragboard ()
and check if the one being dragged with Dragboard.hasFiles ()
is a file.
--Here, ʻacceptTransferModes ()is executed only when
hasFiles ()is
true`, so you can only drop files.
MainController.java
@FXML
public void onDragDropped(DragEvent e) {
System.out.println("onDragDropped()");
Dragboard dragboard = e.getDragboard();
List<File> files = dragboard.getFiles();
files.forEach(file -> System.out.println(file.getName()));
e.consume();
}
--You can get the dragged and dropped files with Dragboard.getFiles ()
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
public class MainController {
@FXML
public void onDragOver(DragEvent e) {
Dragboard dragboard = e.getDragboard();
if (dragboard.hasHtml()) {
e.acceptTransferModes(TransferMode.ANY);
}
e.consume();
}
@FXML
public void onDragDropped(DragEvent e) {
System.out.println("onDragDropped()");
Dragboard dragboard = e.getDragboard();
System.out.println("[html] = " + dragboard.getHtml());
System.out.println("[string] = " + dragboard.getString());
System.out.println("[files] = " + dragboard.getFiles());
e.consume();
}
}
Execution result
Description
MainController.java
@FXML
public void onDragDropped(DragEvent e) {
System.out.println("onDragDropped()");
Dragboard dragboard = e.getDragboard();
System.out.println("[html] = " + dragboard.getHtml());
System.out.println("[string] = " + dragboard.getString());
System.out.println("[files] = " + dragboard.getFiles());
e.consume();
}
--If you drop the text of the browser, you can get the HTML that reproduces the text with getHtml ()
.
--You can get even plain text with getString ()
my-style.css
.drag-over {
-fx-text-fill: red;
}
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
public class MainController {
@FXML
private Label dropLabel;
@FXML
public void onDragEntered() {
this.dropLabel.getStyleClass().add("drag-over");
}
@FXML
public void onDragExited() {
this.dropLabel.getStyleClass().remove("drag-over");
}
...
}
Execution result
Description
--Register the event handlers for DRAG_ENTERED
and DRAG_EXITED
on the node to be dropped.
--Implement to add a style to the node with DRAG_ENTERED
and remove the style with DRAG_EXITED
――By doing this, you can express the waiting for drop more clearly.
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
public class MainController {
@FXML
private Label dragLabel;
@FXML
public void onDragDetected(MouseEvent e) {
Dragboard dragboard = this.dragLabel.startDragAndDrop(TransferMode.ANY);
ClipboardContent content = new ClipboardContent();
content.putString("Drag Test");
dragboard.setContent(content);
e.consume();
}
@FXML
public void onDragOver(DragEvent e) {
e.acceptTransferModes(TransferMode.ANY);
e.consume();
}
@FXML
public void onDragDropped(DragEvent e) {
System.out.println("onDragDropped()");
Dragboard dragboard = e.getDragboard();
System.out.println(dragboard.getString());
e.consume();
}
}
Execution result
Description
MainController.java
@FXML
private Label dragLabel;
@FXML
public void onDragDetected(MouseEvent e) {
Dragboard dragboard = this.dragLabel.startDragAndDrop(TransferMode.ANY);
ClipboardContent content = new ClipboardContent();
content.putString("Drag Test");
dragboard.setContent(content);
e.consume();
}
--Drag start by registering a handler in the DRAG_DETECTED
event
--Execute the startDragAndDrop ()
method of the node to start dragging
--In the argument, specify the transfer mode supported by dragging with TransferMode
.
--On the drop side, the mouse cursor becomes a droppable icon only when the transfer mode that matches the transfer mode set here is specified by ʻacceptTransferModes (). --Set the drag content to
ClipboardContent and set that
ClipboardContentto
Dragboard. --In addition to
putString (), there are
putHtml ()and
putFiles ()etc. --Since
ClipboardContent itself is a map that inherits
HashMap <DataFormat, Object> `, you can save any value.
reference
-JavaFX (SB) Drop Memo (Hishidama's JavaFX2 Scene Builder drop Memo) -7 Drag and drop operation (Release 8)
package sample.javafx;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
public class MainController {
@FXML
public void showDialog() {
...
}
}
Make sure the showDialog ()
method is called when you click the button.
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.showAndWait();
Execution result
--A dialog can be displayed by using the ʻAlert class --Specify the dialog type in the constructor --Type specifies a constant of ʻAlertType
enumeration
--CONFIRMATION
is a confirmation dialog
--Show dialog with showAndWait ()
method
--When displaying a dialog with this method, as in ʻAndWait`, processing is blocked until the dialog is closed.
--Dialog is modal and does not touch the parent window while it is displayed
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
Optional<ButtonType> buttonType = alert.showAndWait();
buttonType.ifPresent(System.out::println);
Execution result
Click the "OK" and "Cancel" buttons respectively.
ButtonType [text=OK, buttonData=OK_DONE]
ButtonType [text=cancel, buttonData=CANCEL_CLOSE]
--The return value of showAndWait ()
is ʻOptional of
ButtonType, so you can see which button was selected when closing. --Return of
showAndWait () when a dialog (such as a dialog with only a" yes "button) that has only a cancel button [^ 1] ** is closed by ʻESC
or the window close button. The value will be ʻOptional.empty () --Strictly speaking, there are cases where
resultConverter` is set, but that area is [Javadoc](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/scene/control/ See Dialog.html)
[^ 1]: ButtonData
is NO
or CANCEL_CLOSE
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Messeji");
alert.showAndWait();
--You can specify the message to be set in the dialog with the second argument of the constructor argument.
INFORMATION
CONFIRMATION
WARNING
ERROR
Alert alert = new Alert(
Alert.AlertType.INFORMATION,
"Messeji",
ButtonType.APPLY,
ButtonType.CANCEL,
ButtonType.CLOSE,
ButtonType.FINISH,
ButtonType.NEXT,
ButtonType.NO,
ButtonType.OK,
ButtonType.PREVIOUS,
ButtonType.YES);
alert.showAndWait();
--ButtonType
can be specified after the third argument of the constructor
--Frequently used buttons are defined as constants in ButtonType
, so if that's all you need, use it.
--You can also make your own (described later)
--Variadic arguments allow multiple ButtonType
s to be specified
--The order of variadic arguments does not become ** the order of button placement **
--A class called ButtonBar
is used for button placement.
--ButtonBar
is a class for button layout, and can reproduce the button layout unique to each OS.
--The order of button arrangement for each OS is ButtonBar Javadoc. Is posted
--The button order ** is determined for each type of button, and the button order determines where the button is placed in ButtonBar
.
--For example, "Yes" and "No" buttons are arranged in the order of "Yes" → "No" on Windows, but in the order of "No" → "Yes" on Mac and Linux.
--As a class that expresses this button type, ButtonData enumeration type Prepared
--This ButtonData
is also set in the constant of the ButtonType
class specified in the constructor of ʻAlert`, and the buttons are arranged according to that value.
** ButtonData ** set to ButtonType constant
ButtonType | ButtonData that is set |
---|---|
APPLY |
APPLY |
CANCEL |
CANCEL_CLOSE |
CLOSE |
CANCEL_CLOSE |
FINISH |
FINISH |
NEXT |
NEXT_FORWARD |
NO |
NO |
OK |
OK_DONE |
PREVIOUS |
BACK_PREVIOUS |
YES |
YES |
--The detailed placement order of ButtonData
for each OS is in Javadoc. Are listed
** Arrangement order of ButtonData
for each OS **
OS | Placement order |
---|---|
Windows | L_E+U+FBXI_YNOCAH_R |
Mac OS | L_HE+U+FBIX_NCYOA_R |
Linux | L_HE+UNYACBXIO_R |
--This mysterious string corresponds to the ** button sequence code **, where each character is assigned to the ButtonData
constant.
--For example, the button sequence code for ButtonData.LEFT
is L
, ButtonData.HELP_2
is ʻE, and so on. --The button sequence code assigned to each constant can be found in the Javadoc for each constant. --To make it a little easier to see, the table below shows the relationship between the
ButtonData` and the constants.
-(F
is not well understood because there is no corresponding constant)
--That is, if you specify ButtonType.CANCEL
in the constructor of ʻAlert, Since
ButtonData in
ButtonType.CANCELis
CANCEL_CLOSE, the cancel button will be inserted in the part where
Cis in the order of
ButtonBar`.
ButtonType myButton = new ButtonType("Button");
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Messeji", myButton);
alert.showAndWait().ifPresent(System.out::println);
Execution result
Console output when clicking the "Botan" button
ButtonType [text=Button, buttonData=OTHER]
--You can place any button by creating a ButtonType
class and passing it to the ʻAlertconstructor. --The button label string can be specified in the
ButtonTypeconstructor. --If nothing is specified,
ButtonData becomes ʻOTHER
--ButtonType
also has a constructor that receives ButtonData
, so you can specify it explicitly.
--The dialog can also be closed with ʻESC, ʻAlt + F4
, the window close button, etc. (for Windows)
--In that case, the return value of showAndWait ()
behaves a little complicated depending on the placed button.
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Messeji", ButtonType.YES);
Optional<ButtonType> buttonType = alert.showAndWait();
String result = buttonType.map(ButtonType::getText).orElse("No selection");
System.out.println(result);
Execution result
Result when closed with ʻESC`
No selection
--Since there is no cancel button [^ 1] in the buttons, the return value will be ʻOptional.empty () `.
--Next, if there are multiple cancel buttons
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Messeji", ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
Optional<ButtonType> buttonType = alert.showAndWait();
String result = buttonType.map(ButtonType::getText).orElse("No selection");
System.out.println(result);
Execution result
1 cup"
2. "No"
3. "Cancel"
4. ESC
5. Alt + F4
6. Close the window
Try closing the dialogs in the order of.
Yes
No
cancel
cancel
cancel
cancel
--When each button is pressed, that button is the return value of showAndWait ()
--If the dialog is closed by a method other than the button, it means that the "Cancel" button has been pressed.
--This is when there are multiple cancel buttons.
ButtonData
is CANCEL_CLOSE
ButtonType
ButtonData.isCancelButton ()
returns true
ButtonType
This is because the return value is determined in the order of priority of --.
--In other words, if the only cancel button is ButtonType.NO
, the return value when the dialog is closed by a method other than the button will be ButtonType.NO
.To summarize the return value of showAndWait ()
when the dialog is closed by a method other than a button,
ButtonType
ButtonType
is determined in the following priority order.ButtonType
where ButtonData
is CANCEL_CLOSE
ButtonData.isCancelButton ()
returns true
ButtonType
Only contentText
can be set in the constructor argument, but the title etc. can be changed after ʻAlert` is generated.
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Take it");
alert.setHeaderText("Header text");
alert.setContentText("Content");
alert.showAndWait();
Each can also be turned off by setting null
.
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle(null);
alert.setHeaderText(null);
alert.setContentText("Content");
alert.showAndWait();
JavaFX GUI processing is executed on a dedicated thread (** UI thread **) called JavaFX Application Thread
.
MainController.java
package sample.javafx;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println("thread = " + Thread.currentThread().getName());
}
}
Execution result
thread = JavaFX Application Thread
If implemented normally, the process will be executed on this UI thread.
However, if time-consuming processing is executed on this UI thread, event processing such as UI update and mouse operation will be waited for. Then, the application cannot accept any user's operation, and the appearance does not change, that is, a so-called solid state.
To avoid this, the time-consuming process must be executed in a thread separate from the UI thread.
JavaFX UI components can only be accessed from UI threads. Exceptions are now thrown when trying to access a UI component from outside the UI thread.
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class MainController {
@FXML
private Button button;
@FXML
public void click() {
new Thread(() -> {
System.out.println("thread = " + Thread.currentThread().getName());
button.setText("hoge");
}).start();
}
}
Execution result
thread = Thread-4
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:279)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.scene.Parent$2.onProposedChange(Parent.java:367)
...
Such restrictions often exist not only in JavaFX, but also in other GUI-related frameworks such as Swing.
The GUI seems to be very difficult to achieve thread safety when running multithreaded. Therefore, most GUI frameworks have a dedicated thread for UI operations, and the UI can only be accessed from that dedicated thread (searching for "GUI, single thread" reveals a lot of stories about that. come).
In JavaFX, as a means to operate UI components from other than UI threads, Platform.runLater () A method called .html # runLater-java.lang.Runnable-) is provided.
MainController.java
package sample.javafx;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class MainController {
@FXML
private Button button;
@FXML
public void click() {
new Thread(() -> {
System.out.println("thread = " + Thread.currentThread().getName());
Platform.runLater(() -> {
System.out.println("runLater thread = " + Thread.currentThread().getName());
button.setText("hoge");
});
}).start();
}
}
Execution result
thread = Thread-4
runLater thread = JavaFX Application Thread
The lambda expression (type Runnable
) passed torunLater ()
will be executed on the UI thread ( JavaFx Application Thread
).
Therefore, you can safely touch the UI thread from the process passed to this runLater ()
.
JavaFX provides a mechanism to make it easier to implement threaded processing.
Worker The thread processing used in JavaFX is abstracted as Worker.
Worker
defines a convenient API not found in regular Thread
.
(This is an interface in itself, and the specific implementation is Task or Service (It is realized by: //docs.oracle.com/javase/jp/8/javafx/api/javafx/concurrent/Service.html)
Workers have a life cycle, and the state transitions as follows.
READY
--When Worker
is created, it first goes into the READY state.
-The only state called ** start state ** is this READY state.
SCHEDULED
--Then, when the start of Worker
is scheduled, it will be in the SCHEDULED state.
--If you are using a thread pool, you may wait in the SCHEDULED state to wait for the pool to become free.
--Depending on the implementation of Worker
, it may be started immediately without being scheduled.
――However, even in that case, it will always be in the RUNNING state after going through SCHEDULED.
--The running
property that represents running becomes true
RUNNING
--The state where Worker
is actually running
--The running
property that represents running becomes true
SUCCEEDED
--This state occurs when the process ends normally.
--The result is set to the value
property
FAILED
--This state occurs when an exception is thrown during execution --Thrown exceptions are set to the ʻexception` property
CANCELLED
--If cancellation is executed before the end state, this state will be reached.
Worker
has [stateProperty ()](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/concurrent/Worker.html#" to get information about these states. Properties such as stateProperty--) and runningProperty () are available. ..
Task
A class called Task is prepared as a class that implements Worker
.
package sample.javafx;
import javafx.concurrent.Task;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@Override
public void initialize(URL location, ResourceBundle resources) {
Runnable task = new Task<Void>() {
@Override
protected Void call() throws Exception {
System.out.println("task.call()");
System.out.println("thread = " + Thread.currentThread().getName());
return null;
}
};
new Thread(task).start();
}
}
Execution result
task.call()
thread = Thread-4
--The Task
class itself is an abstract class, so inherit it and create your own class.
--The call ()
method is an abstract method
--Implement the call ()
method in your own class and describe the process that runs in the background.
--The Task
class implements the Runnable
interface, so you can pass it to Thread
or ʻExecutor` to execute it.
package sample.javafx;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
private Worker worker;
@Override
public void initialize(URL location, ResourceBundle resources) {
Task<Void> task = new Task<Void>() {
@Override
protected Void call() throws Exception {
System.out.println("start...");
final long max = 10000000000000L;
for (long i=0; i<max; i++) {
if (this.isCancelled()) {
System.out.println("cancelled!!");
return null;
}
}
System.out.println("finished");
return null;
}
};
this.worker = task;
new Thread(task).start();
}
@FXML
public void stop() {
this.worker.cancel();
}
}
ʻInitialize ()starts the task, and the
stop ()method registered in the button's event handler calls the
cancel () method of
Worker`.
Execution result
start...
cancelled!!
--You can cancel the execution of Worker
by calling thecancel ()
method of Worker
.
--Cancellation is possible only when the state of Worker
is other than" finished state ".
--Nothing happens when you call cancel ()
in the "finished state"
--When cancel is executed, isCancelled ()
of Task
will return true
.
--You can use this to determine if it was canceled in the call ()
method.
--If canceled during blocking by Thread.sleep ()
etc., catch ʻInterruptedException and enter ʻisCancelled ()
judgment.
--For details, refer to Javadoc of the Task
class.
package sample.javafx;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ProgressBar;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private ProgressBar progressBar;
@Override
public void initialize(URL location, ResourceBundle resources) {
Task<Void> task = new Task<Void>() {
@Override
protected Void call() throws Exception {
final long max = 100000000L;
for (long i=0; i<=max; i++) {
this.updateProgress(i, max);
}
return null;
}
};
this.progressBar.progressProperty().bind(task.progressProperty());
new Thread(task).start();
}
}
Execution result
Description
this.progressBar.progressProperty().bind(task.progressProperty());
--Worker
exposes a property calledprogressProperty ()
--By binding this to progressProperty ()
of ProgressBar
, you can make progress
of Task
and progress
of ProgressBar
work together.
for (long i=0; i<=max; i++) {
this.updateProgress(i, max);
}
--Use the ʻupdateProgress ()method to update the progress of
Task --Pass the number of processed items in the first argument and the total number in the second argument (the method that passes
doubleis also overloaded) --This ʻupdateProgress ()
is safe to run from a non-UI thread
--In which runLater ()
is used
package sample.javafx;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import java.io.IOException;
public class MainController {
@FXML
private Label label;
@FXML
public void start() throws IOException {
MyTask task = new MyTask();
this.label.textProperty().bind(task.valueProperty());
new Thread(task).start();
}
private static class MyTask extends Task<String> {
@Override
protected String call() throws Exception {
for (int i=0; i<100000000; i++) {
this.updateValue("i=" + i);
}
return "finished!!";
}
}
}
Execution result
Description
private static class MyTask extends Task<String> {
@Override
protected String call() throws Exception {
for (int i=0; i<100000000; i++) {
this.updateValue("i=" + i);
}
return "finished!!";
}
}
--Task
has a property called value
--The type of this value
is specified by the type argument of Task
.
--The value of value
can be updated with the ʻupdateValue ()method. --This is also safe to run from non-UI threads --If the execution of
Task is successful, the return value of the
call () method is finally set to the
value` property.
package sample.javafx;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import java.io.IOException;
public class MainController {
@FXML
public void start() throws IOException {
Task<Void> task = new Task<Void>() {
@Override
protected Void call() throws Exception {
return null;
}
};
task.setOnScheduled(event -> System.out.println("scheduled thread=" + Thread.currentThread().getName()));
task.setOnRunning(event -> System.out.println("running thread=" + Thread.currentThread().getName()));
task.setOnSucceeded(event -> System.out.println("succeeded thread=" + Thread.currentThread().getName()));
new Thread(task).start();
}
}
Execution result
scheduled thread=JavaFX Application Thread
running thread=JavaFX Application Thread
succeeded thread=JavaFX Application Thread
--You can register a listener that can monitor the state change with the setOn ****
method.
--The registered listener is executed in the UI thread
package sample.javafx;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import java.io.IOException;
public class MainController {
@FXML
public void start() throws IOException {
Task<Void> task = new Task<Void>() {
@Override
protected Void call() throws Exception {
throw new RuntimeException("test");
}
};
task.setOnFailed(event -> {
Throwable exception = task.getException();
System.out.println("exception=" + exception);
});
new Thread(task).start();
}
}
Execution result
exception=java.lang.RuntimeException: test
--When an exception is thrown in the call ()
method, the thrown exception is set in the ʻexception` property.
――The exception doesn't propagate to the top of the stack, so if you leave it alone, you won't notice that the exception was thrown.
package sample.javafx;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyListProperty;
import javafx.beans.property.ReadOnlyListWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import java.util.concurrent.TimeUnit;
public class MainController {
@FXML
private Label label;
@FXML
public void start() {
MyTask myTask = new MyTask();
this.label.textProperty().bind(myTask.listProperty().asString());
Thread thread = new Thread(myTask);
thread.setDaemon(true);
thread.start();
}
private static class MyTask extends Task<Void> {
private ReadOnlyListWrapper<String> list = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
public final ObservableList<String> getList() {
return list.get();
}
public ReadOnlyListProperty<String> listProperty() {
return list.getReadOnlyProperty();
}
@Override
protected Void call() throws Exception {
for (int i=0; i<10; i++) {
String value = String.valueOf(i);
Platform.runLater(() -> this.list.add(value));
TimeUnit.SECONDS.sleep(1);
}
return null;
}
}
}
--If you want Task
to have ʻObservableList and expose its state to the outside world, you have to be careful to use
Platform.runLater () `to update the list.
Service
package sample.javafx;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.fxml.FXML;
import java.io.IOException;
public class MainController {
private Service<Void> service = new Service<Void>() {
@Override
protected Task<Void> createTask() {
System.out.println("Service.createTask()");
return new Task<Void>() {
@Override
protected Void call() throws Exception {
System.out.println("Task.call()");
return null;
}
};
}
};
@FXML
public void start() throws IOException {
if (!this.service.getState().equals(Worker.State.READY)) {
System.out.println("reset");
this.service.reset();
}
System.out.println("start");
this.service.start();
}
}
Assign the start ()
method to the button's action event and execute it several times.
Execution result
start
Service.createTask()
Task.call()
reset
start
Service.createTask()
Task.call()
reset
start
Service.createTask()
Task.call()
--Task
is supposed to be disposable, and it is not supposed to be used to reuse the same instance again.
--On the other hand, Service
is designed on the assumption that thread processing will be executed many times.
--Service
wraps Task
so that it creates a new instance of Task
and starts a thread each time it executes a thread process.
--Service
is an abstract class, and you can create and use your own class that inherits this class.
--There is createTask ()
as an abstract method, so you need to implement it
--In this method, implement the process to create a new instance of Task
.
--When you execute the start ()
method of Service
, the thread is started and the Task
generated by the createTask ()
method is executed.
When checking the status and re-executing
@FXML
public void start() throws IOException {
if (!this.service.getState().equals(Worker.State.READY)) {
System.out.println("reset");
this.service.reset();
}
System.out.println("start");
this.service.start();
}
--Since Service
implements the Worker
interface, the state of Service
changes according to the rules defined by Worker
.
--That is, it starts with READY
and ends up with SUCCEEDED
, FAILED
, CANCELLED
.
--Execute start ()
of Service
in a state other than READY
will throw ʻIllegalStateException. --There are several ways to return the state to
READY --Execute the
reset () method in the finished state (
SUCCEEDED,
FAILED,
CANCELLED). --For
SCHEDULED,
RUNNING, cancel with
cancel ()and then execute the
reset ()method. ――If you don't want to look back at each state, just execute the
restart ()method. --If the status is
READY, start the process as it is --If it is running, cancel the process and then re-execute. --If it is finished, return it to
READY` and then re-execute.
restart()When rewritten with
@FXML
public void start() throws IOException {
System.out.println("restart");
this.service.restart();
}
--The start ()
, reset ()
, and restart ()
methods must all be executed from the UI thread.
First, check the default behavior.
package sample.javafx;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import java.io.IOException;
public class MainController {
private Service<Void> service = new Service<Void>() {
@Override
protected Task<Void> createTask() {
return new Task<Void>() {
@Override
protected Void call() throws Exception {
Thread thread = Thread.currentThread();
System.out.println("thread.name = " + thread.getName() + ", daemon = " + thread.isDaemon());
return null;
}
};
}
};
@FXML
public void start() throws IOException {
this.service.restart();
}
}
Execute restart ()
several times
Execution result
thread.name = Thread-4, daemon = true
thread.name = Thread-5, daemon = true
thread.name = Thread-6, daemon = true
thread.name = Thread-7, daemon = true
--By default, Service
will create a new daemon thread each time you start Task
.
--If you want to specify the thread to use (for example, use the thread of the thread pool), implement as follows
Specify the thread used by the Service
package sample.javafx;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import java.io.IOException;
import java.util.concurrent.Executors;
public class MainController {
private Service<Void> service = new Service<Void>() {
{
this.setExecutor(Executors.newFixedThreadPool(3));
}
@Override
protected Task<Void> createTask() {
return new Task<Void>() {
@Override
protected Void call() throws Exception {
Thread thread = Thread.currentThread();
System.out.println("thread.name = " + thread.getName() + ", daemon = " + thread.isDaemon());
return null;
}
};
}
};
@FXML
public void start() throws IOException {
this.service.restart();
}
}
Execution result
thread.name = pool-2-thread-1, daemon = false
thread.name = pool-2-thread-2, daemon = false
thread.name = pool-2-thread-3, daemon = false
thread.name = pool-2-thread-1, daemon = false
thread.name = pool-2-thread-2, daemon = false
--Specify Executor in the ʻexecutor property of
Service. --Then
Service will get the thread to execute
Task from ʻexecutor
.
package sample.javafx;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.ProgressBar;
import java.io.IOException;
public class MainController {
@FXML
private ProgressBar progressBar;
private Service<Void> service = new Service<Void>() {
@Override
protected Task<Void> createTask() {
return new Task<Void>() {
@Override
protected Void call() throws Exception {
int max = 10000000;
for (int i=0; i<max; i++) {
this.updateProgress(i, max);
}
return null;
}
};
}
};
@FXML
public void start() throws IOException {
this.service.restart();
this.progressBar.progressProperty().bind(this.service.progressProperty());
}
}
--Service
implements Worker
, so it provides properties such as progress
like Task
.
--Each property is linked to the property of Task
that Service
is executing.
--In other words, referencing the properties of Service
is the same as referencing the properties of the running Task
.
reference
-1 Concurrency in JavaFX (Release 8)
How to apply visual effects such as lighting and drop shadows.
As a mechanism, it seems that the scene graph is once rendered as a pixel image, and image processing is performed to cast shadows or shine light on it.
Try applying various visual effects to the following scene graph and see what happens.
Execution result
Implementation
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.effect.Bloom;
import javafx.scene.layout.Pane;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private Pane pane;
@Override
public void initialize(URL location, ResourceBundle resources) {
Bloom bloom = new Bloom();
this.pane.setEffect(bloom);
}
}
-Bloom Bloom (image looks shining) when using class ) Effect can be applied
--By creating an instance of Bloom
and setting it withsetEffect ()
to the node on the scene graph to which the effect is applied, the visual effect will be applied below that node.
Execution result
Implementation
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.effect.BoxBlur;
import javafx.scene.layout.Pane;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private Pane pane;
@Override
public void initialize(URL location, ResourceBundle resources) {
BoxBlur boxBlur = new BoxBlur();
boxBlur.setWidth(5);
boxBlur.setHeight(5);
this.pane.setEffect(boxBlur);
}
}
-BoxBlur can be used to apply the box blur effect.
--You can specify the blur distance in the vertical and horizontal directions with width
and height
.
--By specifying ʻiterations, you can specify the number of times the blur effect is applied (default is
3`).
Execution result
Implementation
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.effect.MotionBlur;
import javafx.scene.layout.Pane;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private Pane pane;
@Override
public void initialize(URL location, ResourceBundle resources) {
MotionBlur motionBlur = new MotionBlur();
motionBlur.setAngle(45.0);
motionBlur.setRadius(40.0);
this.pane.setEffect(motionBlur);
}
}
-When using MotionBlur, motion blur (like when an object is moving) Blur) can be applied
--You can specify the direction of blur with ʻangle --Specify by the degree method (the one whose angle is expressed by
0.0 to
360.0) ――The horizontal direction is
0.0, and it feels like it rotates clockwise from there. -
―:
0.0 -
\:
45.0 -
|:
90.0 -
/:
135.0 -
―:
180.0 --Specify the blur radius with
radius`
Execution result
Implementation
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.layout.Pane;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private Pane pane;
@Override
public void initialize(URL location, ResourceBundle resources) {
GaussianBlur gaussianBlur = new GaussianBlur();
gaussianBlur.setRadius(10.0);
this.pane.setEffect(gaussianBlur);
}
}
-GaussianBlur can be used to apply Gaussian blur (difference from box blur). I'm not sure)
--You can specify the radius of the blur with radius
Execution result
Implementation
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private Pane pane;
@Override
public void initialize(URL location, ResourceBundle resources) {
DropShadow dropShadow = new DropShadow();
dropShadow.setOffsetX(5.0);
dropShadow.setOffsetY(5.0);
dropShadow.setWidth(20.0);
dropShadow.setHeight(20.0);
dropShadow.setColor(Color.valueOf("CACA00"));
this.pane.setEffect(dropShadow);
}
}
-Using DropShadow has a drop shadow effect. Can be applied
--Specify the relative position (offset) of the shadow with ʻoffsetX, ʻoffsetY
--Use width
and height
to specify the degree of blurring of shadows in the vertical and horizontal directions.
--Specify the shadow color with color
--Use spread
to specify the degree of shadow diffusion ( 0.0
to 1.0
).
Make some changes to the scenegraph to make it easier to see.
Execution result
Implementation
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.effect.InnerShadow;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private Pane pane;
@Override
public void initialize(URL location, ResourceBundle resources) {
InnerShadow innerShadow = new InnerShadow();
innerShadow.setOffsetX(7.0);
innerShadow.setOffsetY(7.0);
innerShadow.setColor(Color.valueOf("500"));
innerShadow.setRadius(10.0);
this.pane.setEffect(innerShadow);
}
}
-If you use InnerShadow, you can use the inner shadow (shadow that looks like a hollow inside). ) Effect can be applied
--Specify the relative position (offset) of the shadow with ʻoffsetX, ʻoffsetY
--Specify the length of the shadow in the vertical and horizontal directions with width
and height
.
--Specify the shadow color with color
--Specify the shadow blur radius with radius
Execution result
Implementation
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.effect.Reflection;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private Label label;
@Override
public void initialize(URL location, ResourceBundle resources) {
Reflection reflection = new Reflection();
reflection.setTopOffset(-25.0);
reflection.setTopOpacity(0.8);
reflection.setFraction(0.8);
this.label.setEffect(reflection);
}
}
-If you use Reflection, it seems that the object is reflected on the floor. ) Effect can be applied
--With topOffset
, specify the relative position (offset) of the upper part of the reflected part.
--With topOpacity
, specify the transparency of the upper part of the reflected part.
--Use fraction
to specify the percentage of the part to be reflected ( 0.0
to 1.0
, the default is 0.75
, that is, only 75% is reflected)
--With bottomOpacity
, specify the transparency at the bottom of the reflected part.
Execution result
Implementation
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Reflection;
import javafx.scene.paint.Color;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private Label label;
@Override
public void initialize(URL location, ResourceBundle resources) {
Reflection reflection = new Reflection();
reflection.setTopOffset(-40.0);
reflection.setTopOpacity(0.8);
reflection.setFraction(0.8);
DropShadow dropShadow = new DropShadow();
dropShadow.setOffsetX(5.0);
dropShadow.setOffsetY(5.0);
dropShadow.setColor(Color.valueOf("AA0"));
reflection.setInput(dropShadow);
this.label.setEffect(reflection);
}
}
--Multiple ʻEffects can be combined by specifying another ʻEffect
withsetInput ()
.
--In the above implementation, the drop shadow is applied first, then the reflection.
--In other words, the one passed by setInput ()
is applied first.
reference
-Visual Effects | 2 Understanding JavaFX Architecture (Release 8) -5 Applying Effects (Release 8)
――If the media data is left as it is, even a few minutes of content will have a large capacity of several hundred MB or several GB. ――Therefore, it is usually compressed in some way --This compression is called ** encoding ** --Encoded media data needs to be restored (decompressed) to its original state when played. --This decompression is called ** decoding ** --A program that encodes and decodes media data is collectively called a ** codec **. ――It seems that some things can only be encoded (decoded). --In order to play media data, there must be a codec that can decode the media data. --There are many types of encoding formats --For audio data, MP3, AAC, WMA, etc ... --For video data, H.264, VP9, DivX, Xvid, etc ..
--In the media file, multiple media data are stored in a combined state.
--For example, in the case of a simple video file, video data and audio data are stored in one file.
--The file that contains the media data is called a ** container **.
--There are multiple types of containers
--For audio files, WAV, AIFF, MP3, etc ...
--MP3 has MP3 as an encoding format and MP3 as a container that can store it (confusing)
--For video files, MP4, FLV, AVI, MOV, etc ...
--The container can store media data in various encoding formats.
--There is not always one type of encoding format that can be stored in one container.
--For example, in the case of a container called MP4
--H.264 or H.265 encoded data can be included in the video data.
--AAC or MP3 encoded data can be included in the audio data.
--In other words, even files in the same container may not be playable depending on the encoding format of the media data contained in them (cannot be played without the corresponding codec).
--The encoding format that can be stored differs depending on the container.
--The actual encoding format of the media data contained in the container can be confirmed by using a tool such as MediaInfo.
--The container type and file extension often match ( .mp4
for MP4, .flv
, .f4v
, etc ... for FLV).
--The media encoding formats and containers supported by JavaFX are Javadoc. ) It is described in
Encode format | voice/Video |
---|---|
PCM | voice |
MP3 | voice |
AAC | voice |
H.264/AVC | Video |
VP6 | Video |
PCM
--Abbreviation for pulse code modulation --Uncompressed data ――Is it inappropriate to call it an encoded format?
** MP3 (encode format) **
--MPEG-1 Abbreviation for Audio Layer-3 --Encoding format for audio used in MPEG-1 of video container --High compression rate and very popular
AAC
--Abbreviation for Advanced Audio Coding --Encoding format for audio created as a successor to MP3 --Higher compression ratio than MP3 --Widely used as audio data for MP4 containers
H.264/AVC
--Encoding format for video that is often used for video data in MP4 containers --There are two names, H.264 and MPEG-4 AVC, which are often referred to as "H.264 / MPEG-4 AVC". --JavaFX's Javadoc says "H.264 / AVC" --Very high compression ratio, widely used --As a successor, there is H.265 with a higher compression ratio.
VP6
--Encoding format for video used in FLV container (Flash movie) --It seems that the compression rate is better than H.264 --As a successor, there is VP9 with a higher compression ratio (it seems to be adopted by Youtube) --However, it seems that Java 9 or later is not recommended
container | Encode format(Video) | Encode format(voice) |
---|---|---|
AIFF | - | PCM |
WAV | - | PCM |
MP3 | - | MP3 |
FLV | VP6 | MP3 |
MP4 | H.264/AVC | AAC |
AIFF
--Abbreviation for Audio Interchange File Format --Container format for MAC original audio files
WAV
--Abbreviation for Waveform Audio Format --Container format for Windows original audio files
** MP3 (container) **
--Container format for audio files containing MP3 (codec) audio data -You can enter meta information such as tags, lyrics, and images.
FLV
--Container format for video files intended for playback on Flash players --FLV can use H.264 as the video codec or ACC as the audio codec depending on the version, but JavaFX only supports the combination of VP6-MP3.
MP4
--Container format for video files --A standard designed in consideration of delivering video in a low-speed line environment such as a mobile phone. --As of 2017, it is very popular
It seems that the widely used encoding formats (MP3, ACC, H.264) are at least supported.
--How to play media files in formats that are not supported by default ――It seems that you can do it if there is a way to add a codec separately, but Is it impossible? ――I read it as if I could play it using WebView, but I couldn't even try it. ――In that case, I feel that I have to convert the format of the media file to the format supported by JavaFX. --There are various free tools that can convert the encoding, both audio and video, so if you convert it to a format supported by JavaFX, you will be able to play it. ――If you search for "audio / video encoding / conversion", you will get various hits.
reference
-Differences from video format types (AVI, MP4, MOV, MPEG, MKV, WMV, FLV, ASF, etc.) [Container] -Differences from voice codec types (MP3, AAC, WMA, WAV, Vorbis, AC3, FLAC, etc.) [Format] -Main formats and features of audio files | Codec / MP3 / AAC / Ogg -Main formats and features of video files | Codec / AVI / MPEG -[Container format-Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%83%95 % E3% 82% A9% E3% 83% BC% E3% 83% 9E% E3% 83% 83% E3% 83% 88 #% E9% 9F% B3% E5% A3% B0% E3% 82% B3% E3% 83% B3% E3% 83% 86% E3% 83% 8A)
package sample.javafx;
import javafx.fxml.FXML;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MainController {
private MediaPlayer player;
@FXML
public void start() {
Path music = Paths.get("./media/music.m4a");
Media media = new Media(music.toUri().toString());
this.player = new MediaPlayer(media);
this.player.play();
}
}
--Media files can be played using the Media
, MediaPlayer
classes, regardless of audio / video.
--The Media
class represents the media to play
--MediaPlayer
provides API for manipulating media
--If you want to display the video, use MediaView
(details will be described later).
--You can play media files with the play ()
method of MediaPlayer
Play ()
, pause ()
, stop ()
are the methods of MediaPlayer
Status | Description |
---|---|
UNKNOWN |
Immediately after creation and reading is not completed |
READY |
Ready to load and play |
PLAYING |
Playing state |
PAUSED |
Paused statePLAYING When you return to, it will resume from where it was stopped. |
STOPPED |
Stopped statePLAYING Play from the beginning when returning to |
STALLED |
Streaming playback does not have enough information to keep the player playing |
HALTED |
A state in which the player cannot play due to an error |
DIPOSED |
Players are destroyed and resources are released |
--MediaPlayer
makes a state transition as shown above.
-- DISPOSED
and HALTED
are omitted because they are in a special state.
--Media files are read asynchronously and may not have been read immediately after the Media
instance was created.
--When the state of MediaPlayer
with which the Media
instance is associated is READY
, the meta information loading is definitely complete.
--In the state of ʻUNKNOWN, playback etc. cannot be performed yet, but instructions such as the
play () method are buffered when called in the state of ʻUNKNOWN
and will be executed when READY
is reached. (You don't have to explicitly wait for READY
)
--For details, refer to Javadoc of MediaPlayer.Status.
Folder structure
`-src/main/
|-java/
| `-sample/javafx/
| :
`-resources/
|-...
`-media/
`-music.mp3
package sample.javafx;
import javafx.fxml.Initializable;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@Override
public void initialize(URL location, ResourceBundle resources) {
URL url = MainController.class.getResource("/media/music.mp3");
Media media = new Media(url.toString());
MediaPlayer player = new MediaPlayer(media);
player.play();
}
}
--You can play the resource file by getting the resource file with ʻURLand doing
toString ()`.
package sample.javafx;
...
public class MainController implements Initializable {
private MediaPlayer player;
...
@Override
public void initialize(URL location, ResourceBundle resources) {
Path music = Paths.get("./media/music.mp3");
Media media = new Media(music.toUri().toString());
this.player = new MediaPlayer(media);
this.player.setCycleCount(3);
}
}
--setCycleCount (int)
can be used to specify the number of times media files should be played continuously.
--If you specify MediaPlayer.INDEFINITE
, it will be played continuously indefinitely.
package sample.javafx;
...
import javafx.util.Duration;
public class MainController implements Initializable {
private MediaPlayer player;
...
@Override
public void initialize(URL location, ResourceBundle resources) {
Path music = Paths.get("./media/music.mp3");
Media media = new Media(music.toUri().toString());
this.player = new MediaPlayer(media);
this.player.setCycleCount(3);
Duration cycleDuration = this.player.getCycleDuration();
System.out.println("cycleDuration=" + cycleDuration);
Duration totalDuration = this.player.getTotalDuration();
System.out.println("totalDuration=" + totalDuration);
this.player.setOnReady(() -> {
System.out.println("[onReady] cycleDuration=" + this.player.getCycleDuration());
System.out.println("[onReady] totalDuration=" + this.player.getTotalDuration());
});
}
}
Execution result
cycleDuration=UNKNOWN
totalDuration=UNKNOWN
[onReady] cycleDuration=9541.0 ms
[onReady] totalDuration=28623.0 ms
--You can get the playback time with cycleDuration
and totalDuration
--cycleDuration
represents the playback time for one time
--totalDuration
represents the total playback time including repetition
--If the number of views is ʻINDEFINITE,
Duration.INDEFINITE --However, if you get it before it becomes
READY, it will become ʻUNKNOWN
.
--Playback time is an instance of a class called Duration
package sample.javafx.property;
import javafx.util.Duration;
public class Main {
public static void main(String[] args) {
Duration duration = new Duration(90000.0);
System.out.println("duration=" + duration);
System.out.println("duration.seconds=" + duration.toSeconds());
System.out.println("duration.minutes=" + duration.toMinutes());
Duration oneMinute = Duration.seconds(60);
System.out.println("oneMinute=" + oneMinute);
System.out.println("oneMinute.minutes=" + oneMinute.toMinutes());
}
}
Execution result
duration=90000.0 ms
duration.seconds=90.0
duration.minutes=1.5
oneMinute=60000.0 ms
oneMinute.minutes=1.0
-Duration is a class that represents the period --A period is a period of time such as minutes or seconds (not ** that represents a specific point in time **). --The period is held with milliseconds as the basic unit. --Specify milliseconds when generating with the constructor --If you use the factory method, you can create an instance by specifying hours, minutes, seconds, etc. --This class is immutable, add () Methods such as multiply () will give the calculation result to another new instance. It is designed to be stored and returned in
--If you want to format the Duration
information into a string (such as 02: 00: 12
), there is nothing in the standard API that can be formatted directly.
--I thought there was a good formatter for Duration of Date and Time API. Not likely ...
--There is no help for it, so if you try to write the Duration
version of JavaFX with reference to the implementation example of StackOverflow in ↑, it looks like ↓
package sample.javafx.property;
import javafx.util.Duration;
public class Main {
public static void main(String[] args) {
Duration duration = new Duration(12294027.0);
String text = format(duration);
System.out.println("text=" + text);
}
public static String format(Duration duration) {
long millis = (long) duration.toMillis();
long absMillis = Math.abs(millis);
long hours = absMillis / 3_600_000;
long minutes = (absMillis / 60_000) % 60;
long seconds = (absMillis / 1_000) % 60;
long milliseconds = absMillis % 1_000;
return (millis < 0 ? "-" : "") + String.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds);
}
}
Execution result
text=03:24:54.027
package sample.javafx;
...
public class MainController implements Initializable {
private MediaPlayer player;
...
@Override
public void initialize(URL location, ResourceBundle resources) {
Path music = Paths.get("./media/music.mp3");
Media media = new Media(music.toUri().toString());
this.player = new MediaPlayer(media);
this.player.setCycleCount(3);
this.player.setStartTime(Duration.seconds(1));
this.player.setStopTime(Duration.seconds(3));
this.player.setOnReady(() -> {
System.out.println("cycleDuration=" + this.player.getCycleDuration());
System.out.println("totalDuration=" + this.player.getTotalDuration());
});
}
}
Execution result
cycleDuration=2000.0 ms
totalDuration=6000.0 ms
--If you set the start time and end time for startTime
and stopTime
, respectively, the media data will be played only within that range.
--cycleDuration
returns the time between startTime
and stopTime
--totalDuration
is cycleDuration * views
Various metadata such as singer, album name, lyrics, and album art can be added to MP3 files. You can access those metadata from JavaFX (FLV metadata seems to be available, but omitted here).
package sample.javafx;
import javafx.collections.ObservableMap;
import javafx.fxml.Initializable;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
...
public class MainController implements Initializable {
@Override
public void initialize(URL location, ResourceBundle resources) {
Path music = Paths.get("./media/music2.mp3");
Media media = new Media(music.toUri().toString());
MediaPlayer player = new MediaPlayer(media);
player.setOnReady(() -> {
ObservableMap<String, Object> metadata = media.getMetadata();
metadata.forEach((key, value) -> {
System.out.println("key=" + key + ", value=" + value);
});
});
}
}
Execution result
key=comment-1, value=iTunPGAP[eng]=0
key=album artist, value=ClariS
key=comment-0, value=[eng]=
key=image, value=javafx.scene.image.Image@1ca0c8fb
key=artist, value=ClariS
key=raw metadata, value={ID3=java.nio.HeapByteBufferR[pos=779441 lim=789675 cap=789675]}
key=composer, value=ryo
key=album, value=SHIORI
key=comment-3, value=iTunSMPB[eng]= 00000000 00000210 0000096C 0000000000E5EE04 00000000 007D1469 00000000 00000000 00000000 00000000 00000000 00000000
key=comment-2, value=iTunNORM[eng]= 000022CF 000021D8 0000C4E5 0000FF84 0002901F 0004CBF5 0000823D 0000832E 0004A049 00049D87
key=genre, value=
key=title, value=A story that you do not know
--You can get ʻObservableMap that holds the metadata with
getMetadata () of
Media`.
--Empty map is returned if there is no metadata or if the container format does not support getting metadata
--The list of supported metadata tags is Javadoc Please refer to that because it is described in.
--Unfortunately AAC does not support retrieving metadata
--I also checked Java9, but AAC is still supported. Not applicable
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.util.Duration;
...
public class MainController implements Initializable {
private MediaPlayer player;
@Override
public void initialize(URL location, ResourceBundle resources) {
Path music = Paths.get("./media/music2.mp3");
Media media = new Media(music.toUri().toString());
this.player = new MediaPlayer(media);
this.player.play();
}
@FXML
public void seek() {
this.player.seek(Duration.minutes(2));
}
}
--Use the seek (Duration seekTime)
method to change the playback position
--The seekTime
specified in the argument specifies the period from the beginning of the media data.
--Even if you specify startTime
, it is not the period from startTime
(probably)
――It looks like ↓ in the figure
--If you specify startTime
, stopTime
and specify seekTime
that goes out of that range, you are restricted to staying in either startTime
or stopTime
.
--For details, refer to Javadoc.
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private MediaView mediaView;
@Override
public void initialize(URL location, ResourceBundle resources) {
Path music = Paths.get("./media/movie.mp4");
Media media = new Media(music.toUri().toString());
MediaPlayer player = new MediaPlayer(media);
this.mediaView.setMediaPlayer(player);
player.play();
}
}
--Use MediaView
to display the video
--Pass the loaded MediaPlayer
tosetMediaPlayer ()
of MediaView
--Width and height can be adjusted with the fitWidth
and fitHeight
properties
AudioClip
For small sound effects with a short playback time, it is more efficient to use ʻAudioClip than
Media`.
package sample.javafx;
import javafx.fxml.Initializable;
import javafx.scene.media.AudioClip;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@Override
public void initialize(URL location, ResourceBundle resources) {
Path music = Paths.get("./media/music2.mp3");
AudioClip audioClip = new AudioClip(music.toUri().toString());
audioClip.play();
}
}
--Instance generation passes URL string as constructor argument like Media
--Playback can be started with the play ()
method
The difference from Media
is as follows.
--Playback with minimum delay (playback immediately)
--You can start using it immediately after instantiation
--Once played, methods other than stop ()
have no effect after that.
--The same instance can be played multiple times at the same time with play ()
--If you call stop ()
while playing multiple sounds at the same time, all the sounds being played will stop.
See Javadoc for details.
Let's create a simple music player while utilizing the contents so far.
package sample.javafx;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.StringBinding;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaPlayer.Status;
import javafx.util.Duration;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ResourceBundle;
import java.util.stream.Stream;
public class SimpleMusicPlayerController implements Initializable {
private MediaPlayer player;
@FXML
private Button playButton;
@FXML
private Button stopButton;
@FXML
private Button pauseButton;
@FXML
private Label volumeLabel;
@FXML
private Label totalTimeLabel;
@FXML
private Label currentTimeLabel;
@FXML
private Slider volumeSlider;
@FXML
private Slider seekSlider;
@FXML
public void play() {
this.player.play();
}
@FXML
public void stop() {
this.player.stop();
}
@FXML
public void pause() {
this.player.pause();
}
@Override
public void initialize(URL location, ResourceBundle resources) {
Media media = new Media(Paths.get("./media/music.m4a").toUri().toString());
this.player = new MediaPlayer(media);
//Volume label
this.volumeLabel.textProperty().bind(this.player.volumeProperty().asString("%.2f"));
//Total play time label
this.player.setOnReady(() -> {
Duration total = this.player.getTotalDuration();
this.totalTimeLabel.setText(this.format(total));
});
//Current play time label
this.currentTimeLabel.textProperty().bind(new StringBinding() {
{bind(player.currentTimeProperty());}
@Override
protected String computeValue() {
Duration currentTime = player.getCurrentTime();
return format(currentTime);
}
});
//Play button
this.playButton.disableProperty().bind(new BooleanBinding() {
{bind(player.statusProperty());}
@Override
protected boolean computeValue() {
boolean playable = playerStatusIsAnyOf(Status.READY, Status.PAUSED, Status.STOPPED);
return !playable;
}
});
//Stop button
this.stopButton.disableProperty().bind(new BooleanBinding() {
{bind(player.statusProperty());}
@Override
protected boolean computeValue() {
boolean stoppable = playerStatusIsAnyOf(Status.PLAYING, Status.PAUSED, Status.STALLED);
return !stoppable;
}
});
//Pause button
this.pauseButton.disableProperty().bind(new BooleanBinding() {
{bind(player.statusProperty());}
@Override
protected boolean computeValue() {
boolean pausable = playerStatusIsAnyOf(Status.PLAYING, Status.STALLED);
return !pausable;
}
});
//Volume slider
this.player.volumeProperty().bind(this.volumeSlider.valueProperty());
this.volumeSlider.setValue(1);
//Seek bar
this.seekSlider.valueProperty().addListener((value, oldValue, newValue) -> {
if (seekSliderIsChanging()) {
Duration seekTime = this.player.getTotalDuration().multiply((Double) newValue);
this.player.seek(seekTime);
}
});
this.player.currentTimeProperty().addListener((value, oldValue, newValue) -> {
if (!seekSliderIsChanging()) {
double totalDuration = this.player.getTotalDuration().toMillis();
double currentTime = newValue.toMillis();
this.seekSlider.setValue(currentTime / totalDuration);
}
});
//Post-processing when playback ends
this.player.setOnEndOfMedia(() -> {
this.player.seek(this.player.getStartTime());
this.player.stop();
});
}
private boolean playerStatusIsAnyOf(Status... statuses) {
Status status = this.player.getStatus();
return Stream.of(statuses).anyMatch(candidate -> candidate.equals(status));
}
private boolean seekSliderIsChanging() {
return this.seekSlider.isValueChanging() || this.seekSlider.isPressed();
}
private String format(Duration duration) {
long millis = (long) duration.toMillis();
long minutes = (millis / 60_000) % 60;
long seconds = (millis / 1_000) % 60;
return String.format("%02d:%02d", minutes, seconds);
}
}
//Current play time label
this.currentTimeLabel.textProperty().bind(new StringBinding() {
{bind(player.currentTimeProperty());}
@Override
protected String computeValue() {
Duration currentTime = player.getCurrentTime();
return format(currentTime);
}
});
...
private String format(Duration duration) {
long millis = (long) duration.toMillis();
long minutes = (millis / 60_000) % 60;
long seconds = (millis / 1_000) % 60;
return String.format("%02d:%02d", minutes, seconds);
}
--Bind to the currentTime
property of MediaPlayer
and set the time-formatted value to the label
//Play button
this.playButton.disableProperty().bind(new BooleanBinding() {
{bind(player.statusProperty());}
@Override
protected boolean computeValue() {
boolean playable = playerStatusIsAnyOf(Status.READY, Status.PAUSED, Status.STOPPED);
return !playable;
}
});
//Stop button
this.stopButton.disableProperty().bind(new BooleanBinding() {
{bind(player.statusProperty());}
@Override
protected boolean computeValue() {
boolean stoppable = playerStatusIsAnyOf(Status.PLAYING, Status.PAUSED, Status.STALLED);
return !stoppable;
}
});
//Pause button
this.pauseButton.disableProperty().bind(new BooleanBinding() {
{bind(player.statusProperty());}
@Override
protected boolean computeValue() {
boolean pausable = playerStatusIsAnyOf(Status.PLAYING, Status.STALLED);
return !pausable;
}
});
...
private boolean playerStatusIsAnyOf(Status... statuses) {
Status status = this.player.getStatus();
return Stream.of(statuses).anyMatch(candidate -> candidate.equals(status));
}
--Use status
of MediaPlayer
to control disabled
of the button
--Determine the state in which each button can be clicked, and set its negation to the disabled
property.
//Volume label
this.volumeLabel.textProperty().bind(this.player.volumeProperty().asString("%.2f"));
...
//Volume slider
this.player.volumeProperty().bind(this.volumeSlider.valueProperty());
this.volumeSlider.setValue(1);
--Volume can be adjusted with the volume
property
--By binding to the value
property of Slider
, slide adjustment and volume adjustment can be linked.
--However, volume
must be specified as a value between 0.0
and 1.0
.
--Therefore, adjust the minimum value (Min
), maximum value (Max
), and increment / decrement amount (Block Increment
) of Slider
according to the range of volume
.
--Volume labels should work with the volume
property of MediaPlayer
--Seekbar implementation is a bit complicated
--There is no property to write the playback position, so you have to register a listener and call MediaPlayer.seek ()
--In addition, the states must be linked in both directions as follows.
--Slider
can be specified between 0.0
and 1.0
, and it is treated as a ratio to the total time.
this.player.currentTimeProperty().addListener((value, oldValue, newValue) -> {
if (!seekSliderIsChanging()) {
double totalDuration = this.player.getTotalDuration().toMillis();
double currentTime = newValue.toMillis();
this.seekSlider.setValue(currentTime / totalDuration);
}
});
...
private boolean seekSliderIsChanging() {
return this.seekSlider.isValueChanging() || this.seekSlider.isPressed();
}
--Register listener in currentTime
--Calculate the ratio of the current time to the total time and set it to the slider value
--The note here is that you have to change the value only when the slider is not ** operated by the user **.
--Otherwise, even if the user tries to move the slider, it will be forced back to the playback time.
--Use the following method in Slider
to determine if the user is touching the slider:
- isValueChanging()
--Returns true
if the slider is being slid
- isPressed()
--Returns true
if the slider is clicked
this.seekSlider.valueProperty().addListener((value, oldValue, newValue) -> {
if (seekSliderIsChanging()) {
Duration seekTime = this.player.getTotalDuration().multiply((Double) newValue);
this.player.seek(seekTime);
}
});
--Register a listener in the value
property of the slider
--Multiply the new value of the slider (0.0
to 1.0
) by the total playback time to find the destination time (seekTime
).
--And use that value to run seek ()
on MediaPlayer
--The note here is that you should only change the value ** when the user is manipulating the slider **.
--Otherwise, this listener will be called even when the slider value is changed according to the playback time, so the playback position will be updated unnecessarily, and the sound will be played jerky.
//Post-processing when playback ends
this.player.setOnEndOfMedia(() -> {
this.player.seek(this.player.getStartTime());
this.player.stop();
});
--status
remains PLAYING
when the playback time reaches stopTime
--In other words, if it is left as it is, the status of the button associated with status
will not change.
--Register a listener with setOnEndOfMedia ()
to explicitly change status
when playback ends
--Here, seek ()
is used to return the playback position to startTime
, andstop ()
is used to change the state to STOPPED
.
--I think it's okay to just use stop ()
, but for some reason getCurrentTime ()
only returns stopTime
.
--MediaPlayer
internally has a flag for boolean
to indicate whether playback has ended, but if that flag is true
,getCurrentTime ()
will return stopTime
. Like
--And if you change the state with just stop ()
after the end of playback, this flag remains as true
, and I feel that currentTime
returns only stopTime
even though it is playing after that.
――I think it's a bug, but I'm not familiar with the specifications, so I can't say anything.
reference
Execution result
Description
--Use MenuBar
to create a menu bar
--There is no limit to where it can be placed, but it is usually placed at the top of the window.
--Use Menu
to add a category (where it says" File ") to MenuBar
--Individual menu items can be defined by adding MenuItem
to Menu
.
package sample.javafx;
import javafx.fxml.FXML;
public class MainController {
@FXML
public void open() {
System.out.println(""Open" is selected");
}
}
--By associating the ʻOn Action` event of the menu with the method of the controller, you can implement the processing when the menu is selected.
Execution result
--You can insert a separator line in the menu by inserting SeparatorMenuItem
.
Execution result
--Submenus can be realized by adding another Menu
under Menu
.
Execution result
--Shortcut keys can be assigned with the ʻaccelerator property of
MenuItem --Shortcut keys are defined by a combination of modifier keys and major keys. --The modifier key is one of
Ctrl,
Shift, ʻAlt
, Meta
, Shortcut
.
--Furthermore, specify one of DOWN
, ʻUP, ʻANY
indicating the state of the key.
--The main keys are ordinary keys such as ʻAand
B. --The modifier key
Shortcutis a key that is often used in shortcuts, and can realize an OS-independent definition. --For Windows, it refers to the
Ctrlkey. For Mac, it refers to the
Meta` key.
package sample.javafx;
import javafx.fxml.FXML;
public class MainController {
@FXML
public void open() {
System.out.println("open");
}
@FXML
public void close() {
System.out.println("close");
}
@FXML
public void fullScreen() {
System.out.println("full screen");
}
}
Each menu is associated with a controller method.
Execution result
――It's a little difficult to understand, but you can operate the menu only with the keyboard without using the mouse. ――As you can see by actually touching it, honestly the movement is suspicious --For Windows, Alt focuses on the menu --After that, you can operate the menu only with the keyboard by entering the characters with underscores for each menu item. --The key assigned to this menu is called ** mnemonic **.
--Two steps are required to assign a mnemonic to a menu
Mnemonic Parsing
Text
, put an underscore ( _
) before the alphabet to which you want to assign a mnemonic.
--Then, the character next to the underscore is judged to be the character to be assigned to the mnemonic.Execution result
--Since mnemonics are assigned in alphabets, you cannot assign mnemonics if the Japanese menu is left as it is (probably)
--In that case, you can add a mnemonic note after the menu text, such as (_F)
, to assign the mnemonic to the Japanese menu as well (or iTunes or other common applications). I referred to it)
Folder structure
`-src/main/
|-java/
| :
`-resources/
`-img/
`-open.png
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.MenuItem;
import javafx.scene.image.ImageView;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private MenuItem openMenuItem;
@Override
public void initialize(URL location, ResourceBundle resources) {
ImageView image = new ImageView("/img/open.png ");
this.openMenuItem.setGraphic(image);
}
}
Execution result
--By setting the graphic
property of MenuItem
to ʻImageView`, you can display any image in the menu item.
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.CheckMenuItem;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private CheckMenuItem checkMenuItem;
@Override
public void initialize(URL location, ResourceBundle resources) {
this.checkMenuItem.selectedProperty().addListener((value, oldValue, newValue) -> {
System.out.println(newValue ? "Checked" : "Unchecked");
});
}
}
Execution result
--You can use CheckMenuItem
to define a menu item to turn the check on and off.
--You can check if the menu is selected with the selected
property
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.ToggleGroup;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private ToggleGroup group1;
@FXML
private RadioMenuItem hogeRadioMenuItem;
@FXML
private RadioMenuItem fugaRadioMenuItem;
@FXML
private RadioMenuItem piyoRadioMenuItem;
@FXML
private ToggleGroup group2;
@FXML
private RadioMenuItem fizzRadioMenuItem;
@FXML
private RadioMenuItem buzzRadioMenuItem;
@Override
public void initialize(URL location, ResourceBundle resources) {
this.hogeRadioMenuItem.setUserData("hoge");
this.fugaRadioMenuItem.setUserData("fuga");
this.piyoRadioMenuItem.setUserData("piyo");
this.group1.selectedToggleProperty().addListener((toggle, oldValue, newValue) -> {
if (newValue != null) {
System.out.println("[group1] " + newValue.getUserData());
}
});
this.fizzRadioMenuItem.setUserData("fizz");
this.buzzRadioMenuItem.setUserData("buzz");
this.group2.selectedToggleProperty().addListener((toggle, oldValue, newValue) -> {
if (newValue != null) {
System.out.println("[group2] " + newValue.getUserData());
}
});
}
}
Execution result
--RadioMenuItem
allows you to define menu items that can be selected exclusively
--Set the identifier (arbitrary character string) of the group to which the item belongs in Toggle Group
of RadioMenuItem
.
--Items with the same group identifier will now be identified as belonging to the same group, and selection will be controlled exclusively.
--Toggle Group
is defined as follows on fxml
Toggle Group on fxml
<RadioMenuItem fx:id="hogeRadioMenuItem" text="Hoge">
<toggleGroup>
<ToggleGroup fx:id="group1" />
</toggleGroup>
</RadioMenuItem>
--fx: id
is the identifier specified by Toggle Group
--So you can inject a ToggleGroup
instance into the controller class with the specified Toggle Group
identifier.
Toggle Group injection
@FXML
private ToggleGroup group1;
--By registering a listener in the selectedToggle
property of ToggleGroup
, you can catch the event when the selected item changes.
--At this time, note that ** one change calls the event twice, and the first time newValue
becomes null
**.
――This seems to be a bug, and it seems that it will be fixed in ver 10.
- [JDK-8189677] RadioMenuItem fires extra NULL value in property - Java Bug System
this.hogeRadioMenuItem.setUserData("hoge");
this.fugaRadioMenuItem.setUserData("fuga");
this.piyoRadioMenuItem.setUserData("piyo");
this.group1.selectedToggleProperty().addListener((toggle, oldValue, newValue) -> {
if (newValue != null) {
System.out.println("[group1] " + newValue.getUserData());
}
});
--Which menu is selected is identified by the value registered in ʻuserData of the menu item instance. --ʻUserData
is of type ʻObject, so you can enter any value ――I don't use it often, but it can be used in situations like this time to identify selected items in a group. --Unfortunately, it seems that you cannot specify ʻuserData
from Scene Builder, so you have to set it with the ʻinitialize ()` method etc.
Execution result
--The context menu (menu that appears by right-clicking) can be realized by adding ContextMenu
to the target node.
--The definition method of menu items is the same as the menu bar.
reference
Main.java
package sample.javafx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.net.URL;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
URL url = this.getClass().getResource("/main.fxml");
FXMLLoader loader = new FXMLLoader(url);
Parent root = loader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setMaximized(true); //★ Maximize
primaryStage.show();
}
}
--You can maximize the window by setting true
withsetMaximized ()
Folder structure
`-src/main/
|-java/
| `-sample/javafx/
| |-MainController.java
| `-OtherController.java
|
`-resources/
|-main.fxml
`-other.fxml
other.fxml
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.URL;
public class MainController {
@FXML
public void openOtherWindow() throws IOException {
//Load fxml and
URL fxml = this.getClass().getResource("/other.fxml");
FXMLLoader loader = new FXMLLoader(fxml);
Pane pane = loader.load();
//Create a scene
Scene scene = new Scene(pane);
//Register the scene on the stage
Stage stage = new Stage();
stage.setScene(scene);
//display
stage.show();
}
}
--Use FXMLLoader
to load fxml
--There are various ways to specify the fxml file, but here it is read using Class.getResource (String)
as the resource file.
--For other loading methods, refer to FXMLLoader Javadoc
--You can get the top node defined in fxml with the load ()
method.
--After that, create a scene stage and start it with the show ()
or showAndWait ()
method.
--JavaFX application consists of ** Stage , ** Scene , ** Scene Graph **
- Stage ** is the top-level container for JavaFX applications and corresponds to windows
--In the above implementation, Stage
corresponds
- Scene ** is a container that holds the scene graph and corresponds to the display area of the stage.
--In the above implementation, Scene
corresponds
-** Scene graph ** is a tree structure that holds all the nodes displayed on the scene, and the display can be changed by rewriting the scene graph.
--In the above implementation, the Pane
loaded from fxml will be the root node for this scenegraph.
MainController.java
package sample.javafx;
public class MainController {
public void hello() {
System.out.println("Hello MainController!!");
}
}
Main.java
package sample.javafx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.net.URL;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
URL url = this.getClass().getResource("/main.fxml");
FXMLLoader loader = new FXMLLoader(url);
Parent root = loader.load();
MainController controller = loader.getController();
controller.hello();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Execution result
Hello MainController!!
--After loading fxml with FXMLLoader.load ()
, you can get an instance of the controller mapped to fxml with the FXMLLoader.getController ()
method.
--Note that null
is returned beforeload ()
** Behavior when no owner is set **
--If you have not set the owner (ʻowner) to
Stage`, closing the first open window will not close the later open window.
** Set owner **
Main.java
package sample.javafx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.net.URL;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
URL url = this.getClass().getResource("/main.fxml");
FXMLLoader loader = new FXMLLoader(url);
Parent root = loader.load();
MainController controller = loader.getController();
controller.setStage(primaryStage); //★ Set the stage of the controller on the controller
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.URL;
public class MainController {
private Stage stage; //★ The stage of this controller is set
public void setStage(Stage stage) {
this.stage = stage;
}
@FXML
public void openOtherWindow() throws IOException {
URL fxml = this.getClass().getResource("/other.fxml");
FXMLLoader loader = new FXMLLoader(fxml);
Pane pane = loader.load();
Scene scene = new Scene(pane);
Stage stage = new Stage();
stage.setScene(scene);
stage.initOwner(this.stage); //★ Set owner
stage.showAndWait();
}
}
Execution result
--If you set the owner, when the parent window is closed, the child window will be closed as well.
--If you are loading the scenegraph from fxml, there are several ways to get your own Stage
in the corresponding controller, but get an instance of the controller from FXMLLoader
and set it via a setter etc. It feels kind of smart to do
--Use the ʻinitOwner () `method to set the owner
――By the way, even if you set the owner, it is not modal.
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.URL;
public class MainController {
private Stage stage;
public void setStage(Stage stage) {
this.stage = stage;
}
@FXML
public void openOtherWindow() throws IOException {
URL fxml = this.getClass().getResource("/other.fxml");
FXMLLoader loader = new FXMLLoader(fxml);
Pane pane = loader.load();
Scene scene = new Scene(pane);
Stage stage = new Stage();
stage.setScene(scene);
stage.initOwner(this.stage);
stage.initModality(Modality.WINDOW_MODAL); //★ Set Modality
stage.showAndWait();
}
}
Execution result
--ʻInitModality () can make the stage modal --Pass a constant of
Modality enumeration to ʻinitModality ()
--NONE
, WINDOW_MODAL
, ʻAPPLICATION_MODALare defined in
Modality`.
constant | meaning |
---|---|
NONE |
Do not block other windows |
WINDOW_MODAL |
Block the owner's window (Do not block windows other than the owner) |
APPLICATION_MODAL |
Block all windows |
reference
-[Basic structure of JavaFX | Light Lab](http://krr.blog.shinobi.jp/javafx/javafx%E3%81%AE%E5%9F%BA%E6%9C%AC%E6%A7%8B % E9% 80% A0) -1 JavaFX Scene Graph Operation (Release 8)
--If you create a complicated screen with a single fxml, the implementation will be messed up including the controller class.
――Also, when the same part is reused in multiple places, it is not good because duplication occurs if it is made with a single fxml.
--fxml provides a tag called <fx: include>
to embed other fxml
--If you use this, you can avoid the above problems.
** Folder structure **
`-src/main/resources/
|-embedded.fxml
`-main.fxml
** Embed fxml (embedded.fxml) **
** Embed destination fxml (main.fxml) **
main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.text.Font?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8.0.151" xmlns:fx="http://javafx.com/fxml/1">
<top>
<Label text="Main" BorderPane.alignment="CENTER">
<font>
<Font size="40.0" />
</font>
</Label>
</top>
<center>
<fx:include source="embedded.fxml" />
</center>
</BorderPane>
Description
--Specify the fxml file to be embedded in the source
attribute of<fx: include>
--This value seems to be a path on the classpath if it starts with /
-The reference says "It is considered relative to the classpath" and the meaning is difficult to understand. .. ..
--If it doesn't start with /
, it seems to be a relative path from the location of the embedded fxml file.
――This is also difficult to understand because the reference says "It is considered to be relative to the path of the current document". .. ..
--If you are using SceneBuilder, you must write it with a path without /
to preview it.
--If you add the folder where the target fxml is placed to the classpath of SceneBuilder itself, it will work, but normally you will not start it like that ...
** Attempts to include in SceneBuilder fail **
--SceneBuilder seems to have a menu to include fxml (File-> Include-> FXML)
--But when I try to include fxml using this, it fails to include with Failed to include'***. Fxml'
――Sometimes you succeed, but I'm not sure
--It doesn't seem that SceneBuilder doesn't support include, so if you modify fxml directly by hand and write <fx: include>
, it will preview properly.
--I don't know the cause, but for the time being, it seems safe to handwrite only <fx: include>
.
--After embedding <fx: include>
by hand, you can operate from SceneBuilder normally.
file organization
`-src/main/
|-java/
| `-sample/javafx/
| |-Main.java
| |-MainController.java
| `-EmbeddedController.java
`-resources/
|-main.fxml
`-embedded.fxml
embedded.fxml
EmbeddedController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class EmbeddedController {
@FXML
private Label label;
public void setMessage(String message) {
this.label.setText(message);
}
}
main.fxml
main.fxml
...
<BorderPane fx:id="pane" ... fx:controller="sample.javafx.MainController">
<top>
<Label text="Main" BorderPane.alignment="CENTER">
...
</Label>
</top>
<center>
<fx:include fx:id="embedded" source="embedded.fxml" />
</center>
</BorderPane>
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private EmbeddedController embeddedController;
@Override
public void initialize(URL location, ResourceBundle resources) {
this.embeddedController.setMessage("Hello!!");
}
}
Execution result
Description
--The controller (ʻEmbeddedController) associated with the FXML (ʻembedded.fxml
) embedded with<fx: include>
is the embedding destination.
You can inject into the controller (MainController
) of FXML (main.fxml
) with @FXML
--However, the fx: id
attribute must be set in<fx: include>
.
--If fx: id
is not set, the controller instance will not be injected and will be null
.
embedded.fxml
EmbeddedController.java
package sample.javafx;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import java.net.URL;
import java.util.ResourceBundle;
public class EmbeddedController implements Initializable {
private IntegerProperty count = new SimpleIntegerProperty(0);
@FXML
private Label label;
@FXML
public void countUp() {
int now = this.count.get();
this.count.set(now + 1);
}
@Override
public void initialize(URL location, ResourceBundle resources) {
StringBinding output = this.count.asString("count = %d");
this.label.textProperty().bind(output);
}
}
main.fxml
main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.text.Font?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8.0.151" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.javafx.MainController">
<fx:define>
<fx:include fx:id="embeddedPane" source="embedded.fxml" />
</fx:define>
<top>
<Label text="Main" BorderPane.alignment="CENTER">
<font>
<Font size="40.0" />
</font>
</Label>
</top>
<center>
<Button mnemonicParsing="false" onAction="#openWindow" text="Open Window" BorderPane.alignment="CENTER">
<font>
<Font size="25.0" />
</font>
</Button>
</center>
</BorderPane>
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.net.URL;
import java.util.ResourceBundle;
public class MainController implements Initializable {
@FXML
private Pane embeddedPane;
private Stage embeddedWindow;
@Override
public void initialize(URL location, ResourceBundle resources) {
Scene scene = new Scene(this.embeddedPane);
this.embeddedWindow = new Stage();
this.embeddedWindow.setScene(scene);
this.embeddedWindow.initModality(Modality.APPLICATION_MODAL);
}
@FXML
public void openWindow() {
this.embeddedWindow.showAndWait();
}
}
Execution result
Description
main.fxml
...
<BorderPane ... fx:controller="sample.javafx.MainController">
<fx:define>
<fx:include fx:id="embeddedPane" source="embedded.fxml" />
</fx:define>
...
</BorderPane>
--Embed the FXML (ʻembedded.fxml) to be embedded using
<fx: include> in the FXML (
main.fxml) to be embedded. --At this time, if you embed it normally, it will be visible in the scene, so embed it in
<fx: define>. ――This way, the embedded tags will not be displayed visually. --Set
fx: id attribute to
<fx: include> `(required to DI to controller)
MainController.java
package sample.javafx;
...
public class MainController implements Initializable {
@FXML
private Pane embeddedPane;
private Stage embeddedWindow;
@Override
public void initialize(URL location, ResourceBundle resources) {
Scene scene = new Scene(this.embeddedPane);
this.embeddedWindow = new Stage();
this.embeddedWindow.setScene(scene);
this.embeddedWindow.initModality(Modality.APPLICATION_MODAL);
}
@FXML
public void openWindow() {
this.embeddedWindow.showAndWait();
}
}
--DI embed FXML (ʻembedded.fxml) with
@FXML in the embed destination controller (
MainController.java) (ʻembeddedPane
)
--If you create a Stage
using ʻembeddedPane, you can display the window with
show () or
showAndWait () . --The life cycle of the embedded FXML controller (ʻEmbeddedController
) is the same as the embedded controller.
--If you want to create a new instance each time it is displayed, I think that you will load it with FXMLLoader
each time.
reference
--Click the button to display the file selection dialog
Main.java
package sample.javafx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.net.URL;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
URL url = this.getClass().getResource("/main.fxml");
FXMLLoader loader = new FXMLLoader(url);
Parent root = loader.load();
MainController controller = loader.getController();
controller.setStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
--Get an instance of MainController
from FXMLLoader
and set primaryStage
MainController.java
package sample.javafx;
import javafx.fxml.FXML;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
public class MainController {
private Stage stage;
@FXML
public void openFileDialog() {
FileChooser chooser = new FileChooser();
File file = chooser.showOpenDialog(this.stage);
System.out.println("file=" + file);
}
public void setStage(Stage stage) {
this.stage = stage;
}
}
Execution result
Displayed dialog
Select a file and click "Open"
Console output
file=C:\Program Files\Java\jdk-9.0.1\README.html
Description
FileChooser chooser = new FileChooser();
File file = chooser.showOpenDialog(this.stage);
System.out.println("file=" + file);
--Use the FileChooser class to display the file selection dialog.
--Create an instance and execute the showOpenDialog (Stage)
method to open a file selection dialog for selecting a single file.
--Specify the owner Stage
as the argumentStage
.
--If an owner is specified, operations on the owner are blocked (become modal) while the file selection dialog is open.
--You can also pass null
, in which case the file selection dialog will not be modal
--The showOpenDialog ()
method is blocked while the dialog is displayed
--Dialog selection results are returned as File
type
--If a file is selected, a File
object pointing to that file is returned.
--null
is returned if the dialog is closed without selecting a file
FileChooser chooser = new FileChooser();
chooser.setTitle("File laundry");
File file = chooser.showOpenDialog(this.stage);
Execution result
--You can specify the dialog title with setTitile (String)
FileChooser chooser = new FileChooser();
chooser.setInitialDirectory(new File("C:/Program Files/java/jdk-9.0.1"));
File file = chooser.showOpenDialog(this.stage);
--setInitialDirectory (File)
allows you to specify the directory to display when the dialog is opened.
--If nothing is specified, the default directory will be displayed for each environment (OS).
――It seems that the FileChooser
class itself does not have a function to record the directory (file) opened last time.
--So, if you want to reopen the directory you opened last time, you can do it by remembering the directory you opened separately and specifying the directory with setInitialDirectory ()
when opening the dialog again.
package sample.javafx;
import javafx.collections.ObservableList;
import javafx.stage.FileChooser.ExtensionFilter;
...
public class MainController {
private Stage stage;
@FXML
public void openFileDialog() {
FileChooser chooser = new FileChooser();
ObservableList<ExtensionFilter> extensionFilters = chooser.getExtensionFilters();
extensionFilters.add(new ExtensionFilter("Anything", "*.*"));
extensionFilters.add(new ExtensionFilter("Only with images", "*.jpg ", "*.jpeg ", "*.png ", "*.gif"));
extensionFilters.add(new ExtensionFilter("Rainy day video", "*.mp4"));
File file = chooser.showOpenDialog(this.stage);
System.out.println("file=" + file);
}
...
}
Execution result
--You can get ʻObservableList which defines the extension by extension with
getExtensionFilters () . --By adding ʻExtensionFilter
to this list, you can specify the refinement by extension.
--- Pass "description" and "extension definition" to the constructor of ʻExtensionFilter --"Extension definition" is specified in the format
. --If there is anything, set it to
FileChooser chooser = new FileChooser();
List<File> files = chooser.showOpenMultipleDialog(this.stage);
if (files == null) {
System.out.println("files=" + files);
} else {
files.forEach(System.out::println);
}
Execution result
Console output
C:\Program Files\Java\jdk-9.0.1\COPYRIGHT
C:\Program Files\Java\jdk-9.0.1\README.html
C:\Program Files\Java\jdk-9.0.1\release
--When you open a dialog with showOpenMultipleDialog (Stage)
, a dialog where you can select multiple files opens.
--Selection result is returned by List <File>
--Returns null
if nothing is selected (isn't it an empty list ...)
FileChooser chooser = new FileChooser();
File file = chooser.showSaveDialog(this.stage);
System.out.println("file=" + file);
Execution result
Console output
file=C:\Users\Public\hoge
--You can use showSaveDialog (Stage)
to open a dialog to select where to save the file.
--The return value will be a File
object that points to the user-specified destination file.
--If the dialog is closed without selecting anything, null
is returned.
package sample.javafx;
import javafx.stage.DirectoryChooser;
...
public class MainController {
private Stage stage;
@FXML
public void openFileDialog() {
DirectoryChooser chooser = new DirectoryChooser();
File directory = chooser.showDialog(this.stage);
System.out.println("directory=" + directory);
}
...
}
Execution result
Console output
directory=C:\Users\Public\Downloads
--Use DirectoryChooser to open the directory selection dialog.
--The usage is the same as the basic FileChooser
except that the selection target is a directory (such as ʻinitialDirectory`).
reference
Appearance
Construction
** Size setting of each vertical element **
--Use VBox
if you want to line up vertically with the same width
--Each element to be arranged is extended to the maximum width with the maximum width (Max Width
) set to MAX_VALUE
.
--Use HBox
if you want to line up side by side at the same height
--Each element to be lined up is extended to the maximum height with the maximum height (Max Height
) set to MAX_VALUE
.
Recommended Posts