How to use a FontAwesomeIconView object as a Stage's icon? - javafx

I am using FontAwesomeFX for many icons throughout my application. I would like to use them as icons for my Stage as well.
Since FontAwesomeIconView extends GlyphIcon which extends Text, I can not use it as an Image directly.
Is there a way to create a usable Image from a Text object?
I have tried to use snapshot, but I am not familiar with that method and end up with the standard "empty" icon on my stage.
Here is the MCVE I have so far:
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon;
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FontAwesomeIconExample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple Interface
VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
// Convert the FontAwesomeIconView node to an Image
FontAwesomeIconView iconView = new FontAwesomeIconView(FontAwesomeIcon.ID_CARD_ALT);
WritableImage icon = iconView.snapshot(new SnapshotParameters(), null);
primaryStage.getIcons().add(icon);
// Show the stage
primaryStage.setWidth(300);
primaryStage.setHeight(100);
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Sample");
primaryStage.show();
}
}
And the result:
Is it possible to use a node snapshot as a stage icon? Or is there a better method of converting the FontAwesomeIconView for this purpose?
EDIT:
I have tried both Sedrick's and JKostikiadis's methods and both give similar results for me.
Sedrick's:
// Convert the FontAwesomeIconView node to an Image
FontAwesomeIconView iconView = new FontAwesomeIconView(FontAwesomeIcon.AMAZON);
WritableImage icon = iconView.snapshot(new SnapshotParameters(), null);
java.awt.image.BufferedImage bImage = SwingFXUtils.fromFXImage(icon, null);
ByteArrayOutputStream s = new ByteArrayOutputStream();
try {
javax.imageio.ImageIO.write(bImage, "png", s);
} catch (IOException e) {
e.printStackTrace();
}
JKostikiadis's:
FontAwesomeIconView iconView = new FontAwesomeIconView(FontAwesomeIcon.AMAZON);
WritableImage writableImg = iconView.snapshot(null, null);
Image img = SwingFXUtils.toFXImage(SwingFXUtils.fromFXImage(writableImg, null), null);
primaryStage.getIcons().add(img);
I believe they both accomplish essentially the same thing (with the sizing of the Image resulting in different displayed icons).
What I do not understand now, is why this is the icon it is producing for me and not them...
I am running Windows 7 and JDK 1.8.0_161.

I just figured it out. The goal is to get an InputStream. I was able to do that using SwingFXUtils.fromFXImage(icon, null); to get a bufferedImage. From there I used ImageIO.write(bImage, "png", s); to get a byte array. I used to byte array to get an InputStream.
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon;
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
public class FontAwesomeIconExample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
try {
// Simple Interface
VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
// Convert the FontAwesomeIconView node to an Image
FontAwesomeIconView iconView = new FontAwesomeIconView(FontAwesomeIcon.AMAZON);
WritableImage icon = iconView.snapshot(new SnapshotParameters(), null);
BufferedImage bImage = SwingFXUtils.fromFXImage(icon, null);
ByteArrayOutputStream s = new ByteArrayOutputStream();
ImageIO.write(bImage, "png", s);
InputStream is = new ByteArrayInputStream(s.toByteArray());
primaryStage.getIcons().add(new Image(is, 16, 16, false, false));
// Show the stage
primaryStage.setWidth(300);
primaryStage.setHeight(100);
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Sample");
primaryStage.show();
} catch (IOException ex) {
Logger.getLogger(FontAwesomeIconExample.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
This is worth noting from the javadoc.
The images should be different sizes of the same image and the best size will be chosen, eg. 16x16, 32x32.

In addition to Sedrick's answer, another (shorter) solution could be to just create an Image (javafx package) and add it stage icons :
FontAwesomeIconView iconView = new FontAwesomeIconView(FontAwesomeIcon.AMAZON);
WritableImage writableImg = iconView.snapshot(null, null);
Image img = SwingFXUtils.toFXImage(SwingFXUtils.fromFXImage(writableImg, null), null);
primaryStage.getIcons().add(img);
P.S A relative post could be found here : Snapshot Image can't be used as stage icon
Edit:
For some reason, the version 8.15 of the FontawesomeFx require the FontAwesomeIconView to be added on a Scene in order to initialize its content, something that is not necessary to do on the newest version of FontawesomeFx. So, in my opinion, you have to either upgrade your FontawesomeFx version or to add the FontAwesomeIconView on a Scene and after to take the snapshot. Like this :
FontAwesomeIconView iconView = new FontAwesomeIconView(FontAwesomeIcon.ID_CARD_ALT);
Scene scene = new Scene(new StackPane(iconView));
WritableImage writableImg = iconView.snapshot(null, null);
Image img = SwingFXUtils.toFXImage(SwingFXUtils.fromFXImage(writableImg, null), null);
Here is the result (using 8.5 version):

Related

Updating the Width of TextField and VBox when Full screened JavaFX

whenever I try to full screen my application, it doesn't scale. I've made multiple copies of this application trying different methods but none seem to work right.
First attempt: Application was a Parent, it would scale the background but the elements inside wouldn't scale to screen size.
As an update: here is the actual Parent that was made. The layout is the original one I wrote and has no issues when it's windowed. It has a preset WIDTH and HEIGHT but when full screened, The first example picture is what it looks like where the WIDTH of the the TextField doesn't update (since it's preset and not updating to the highest WIDTH of the screen it's running on). There are two parts to this that CAN be fixed when only one is fixed. The displayed Text has a set wrapping length of the console, though it is set by using WIDTH.
Here's what the console looks like when it's windowed:
If I could find a way to change the WIDTH, I'm thinking this can be fixed for both the TextField and the setWrappingWidth().
package application.console;
import application.areas.startingArea.SA;
import application.areas.vanguardForest.VFCmds;
import application.areas.vanguardForest.VFNavi;
import application.areas.vanguardForest.VFPkups;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Parent;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.control.TextField;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class Ce extends Region {
public static boolean fullscreen = false;
public static double WIDTH = 990;
// 990;
// Screen.getPrimary().getBounds().getMaxX();
public static double HEIGHT = 525;
// 525;
// Screen.getPrimary().getBounds().getMaxY();
public static Font Cinzel = (Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 16));
public static VBox console = new VBox(2);
public static TextField input = new TextField();
public static ScrollPane scroll = new ScrollPane();
public static BorderPane root = new BorderPane();
public static String s;
public static Parent Window() {
root.setMinSize(WIDTH, (HEIGHT - input.getHeight()));
root.setStyle("-fx-background-color: #232323;");
scroll.setContent(console);
root.setCenter(scroll);
scroll.setStyle("-fx-background: #232323;"
+ "-fx-background-color: transparent;"
+ "-fx-border-color: #232323;"
+ "-fx-focus-color: #232323;"
);
scroll.setHbarPolicy(ScrollBarPolicy.NEVER);
scroll.setVbarPolicy(ScrollBarPolicy.NEVER);
scroll.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));
console.setStyle("-fx-background-color: #232323;"
+ "-fx-focus-color: #232323;");
console.heightProperty().addListener(new ChangeListener<Object>() {
#Override
public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) {
scroll.setVvalue((Double)newValue);
}
});
HBox hbox = new HBox();
hbox.setPrefSize(WIDTH, 16);
root.setBottom(hbox);
Text carrot = new Text(" >");
carrot.setFont(Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 26));
carrot.setFill(Color.WHITE);
input.setStyle("-fx-background-color: transparent;"
+ "-fx-text-fill: #FFFFFF;"
+ "-fx-highlight-fill: #FFFFFF;"
+ "-fx-highlight-text-fill: #232323;"
// + "-fx-border-color: #FFFFFF;"
// + "-fx-border-width: .5;"
);
input.setFont(Cinzel);
input.setMinWidth(console.getWidth());
input.setOnAction(e -> {
String s = (input.getText()).stripTrailing();
input.clear();
});
Pane pane = new Pane();
root.getChildren().add(pane);
hbox.getChildren().addAll(carrot, input);
return root;
}
This isn't the main issue as I've stated, once getting the scaled width for the TextField the process of for setWrappingWidth() for displaying the text should be the if a solution is found, here's how it goes:
#SuppressWarnings("static-access")
public void print(String s, Color c) {
Ce Ce = new Ce();
HBox text1 = new HBox();
text1.setMinWidth(Ce.WIDTH);
text1.setMaxWidth(Ce.WIDTH);
Text tCarrot = new Text(" > ");
tCarrot.setFont(Ce.Cinzel);
tCarrot.setFill(c);
Text text2 = new Text();
final IntegerProperty i = new SimpleIntegerProperty(0);
Timeline tl = new Timeline();
KeyFrame kf = new KeyFrame(
Duration.seconds(textSpeed(fastText)),
e1 -> {
if(i.get() > s.length()) {
tl.stop();
} else {
text2.setText(s.substring(0, i.get()));
i.set(i.get() + 1);
}
});
tl.getKeyFrames().add(kf);
tl.setCycleCount(Animation.INDEFINITE);
tl.play();
text2.setFill(c);
text2.setFont(Ce.Cinzel);
text2.setWrappingWidth(Ce.WIDTH - 40);
text1.getChildren().addAll(tCarrot, text2);
Ce.console.getChildren().add(text1);
Ce.console.setMargin(text1, new Insets(5, 0, 0, 3));
}
Lastly, the HEIGHT of the VBox for the displayed Text works just as intended, it's just the setting/updating the WIDTH to set it to the size of the window whether Windowed of Full screened that is the main issue here.
Try this app. It will not be exactly what you want but may provide some useful help for you if you study it, if not just ignore it, tears can keep you blind, and sometimes, that is ok.
The implementation follows the suggestions you have received in the comments on your questions which together explain what is being done and why, so I won't provide much commentary on the solution here.
Type text in the input bar, press enter and it will appear in the listview for the console log. Use the Toggle full-screen button to toggle full-screen mode on or off.
Console.java
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Console extends VBox {
private final ObservableList<String> consoleLog = FXCollections.observableArrayList();
private final ListView<String> logView = new ListView<>(consoleLog);
public Console(Stage stage) {
VBox.setVgrow(logView, Priority.ALWAYS);
HBox ribbon = createRibbon(
createFullScreenToggle(stage)
);
ribbon.setMinHeight(HBox.USE_PREF_SIZE);
getChildren().addAll(
ribbon,
logView
);
}
private ToggleButton createFullScreenToggle(Stage stage) {
ToggleButton fullScreenToggle = new ToggleButton("Toggle full screen");
fullScreenToggle.setOnAction(e ->
stage.setFullScreen(
fullScreenToggle.isSelected()
)
);
return fullScreenToggle;
}
private HBox createRibbon(ToggleButton fullscreenToggle) {
Text prompt = new Text(">");
TextField input = new TextField();
input.setOnAction(e -> {
consoleLog.add(0, input.getText());
logView.scrollTo(0);
input.clear();
});
HBox.setHgrow(input, Priority.ALWAYS);
HBox ribbon = new HBox(10,
prompt,
input,
fullscreenToggle
);
ribbon.setAlignment(Pos.BASELINE_LEFT);
return ribbon;
}
public ObservableList<String> getConsoleLog() {
return consoleLog;
}
}
ConsoleApplication.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class ConsoleApplication extends Application {
#Override
public void start(Stage stage) {
Console console = new Console(stage);
console.getConsoleLog().addAll(
TEXT.lines().toList()
);
stage.setScene(
new Scene(
console
)
);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private static final String TEXT = """
W. Shakespeare - Sonnet 148
O me, what eyes hath Love put in my head,
Which have no correspondence with true sight!
Or, if the have, where is my judgement fled,
That censures falsely what they see aright?
If that be fair whereon my false eyes dote,
What means the world to say it is not so?
If it be not, then love doth well denote
Love’s eye is not so true as all men’s ‘No.’
How can it? O, how can Love’s eye be true,
That is so vex’d with watching and with tears?
No marvel then, though I mistake my view;
The sun itself sees not till heaven clears.
O cunning Love! with tears thou keep’st me blind.
Lest eyes well-seeing thy foul faults should find.
""";
}
If you want to increase the nodes height/width according to the viewport, then this's not the best practice, because every user will have the same font size at the end. What you can do is to make the font resizable by either GUI buttons or keyboard/mouse keys.
Here is a modification on your code, that will allow users to use ctrl + mouse wheel to increase/decrease the font (like any browser or terminal):
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class ConsoleTest extends Application {
#Override
public void start(Stage stage) {
Scene scene = new Scene(new GameWindow().Console(), 600, 600);
stage.setTitle("Console");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
class GameWindow {
public static Console c = new Console();
public Parent Console() {
for (int i = 0; i < 100; i++) c.addText(new Text("Test" + i));
return c;
}
}
class Console extends BorderPane {
private final SimpleDoubleProperty fontSize = new SimpleDoubleProperty(20);
private final ObjectBinding<Font> fontBinding = Bindings.createObjectBinding(() -> Font.font(fontSize.get()), fontSize);
private final VBox console;
public Console() {
console = new VBox();
console.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
ScrollPane scroll = new ScrollPane(console);
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
scroll.setPadding(Insets.EMPTY);
Text caret = new Text(" >");
caret.fontProperty().bind(fontBinding);
caret.setFill(Color.WHITE);
TextField input = new TextField();
input.setStyle("-fx-background-color: transparent;" + "-fx-text-fill: #FFFFFF;" + "-fx-highlight-fill: #FFFFFF;" + "-fx-highlight-text-fill: #232323;");
input.fontProperty().bind(fontBinding);
HBox inputBar = new HBox(2, caret, input);
inputBar.setStyle("-fx-background-color: #232323;");
inputBar.setAlignment(Pos.CENTER_LEFT);
setCenter(scroll);
setBottom(inputBar);
EventHandler<ScrollEvent> scrollEvent = e -> {
if (e.isControlDown()) {
if (e.getDeltaY() > 0) {
fontSize.set(fontSize.doubleValue() + 2);
} else {
double old;
fontSize.set((old = fontSize.doubleValue()) < 10 ? old : old - 2);
}
e.consume();
}
};
inputBar.setOnScroll(scrollEvent);
console.setOnScroll(scrollEvent);
}
public void addText(Text text) {
text.fontProperty().bind(fontBinding);
text.setFill(Color.WHITE);
console.getChildren().add(text);
}
}

How to use modal dialog in this case?

I have a question. I need to make a GridPane with a directory choose that will then lead me to a modal dialog showing photos. I cannot figure how to do the modal dialog that also has to be a GridPane or a HBox...so the question is , how do I get to show a Modal Dialog after selecting the Folder and pressing the "Show" Button... Thanks a lot!
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
public class FotoView extends Application {
#Override
public void start(Stage primaryStage) {
TextField tf = new TextField();
Button b1 = new Button("Search");
Button b2 = new Button("Show");
DirectoryChooser dc = new DirectoryChooser();
GridPane gp = new GridPane();
gp.add(tf, 0 , 0);
gp.add(b1, 1, 0);
gp.add(b2, 0, 1);
b1.setOnAction(e-> dc.showDialog(primaryStage));
primaryStage.setScene(new Scene(gp)) ;
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
} ```
Below is a quick example where a first window has a button that opens up a DirectoryChooser. Once a directory has been selected a second smaller window opens up with the Modality set to APPLICATION_MODAL. In this second window you could add the image(s) that you load and add them to the GridPane.
import java.io.File;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.DirectoryChooser;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(final String[] args) {
launch(args);
}
#Override
public void start(final Stage aStage) throws Exception {
final HBox root = new HBox();
final Button browseBtn = new Button("Click to open a Directory chooser");
root.getChildren().add(browseBtn);
browseBtn.setOnAction(e -> {
final DirectoryChooser chooser = new DirectoryChooser();
final File dir = chooser.showDialog(aStage);
openNewModalStage(aStage, dir);
});
final Scene scene = new Scene(root, 500, 500);
aStage.setScene(scene);
aStage.show();
}
private void openNewModalStage(final Stage aStage, final File aDirectory) {
final Stage stage = new Stage();
final GridPane grid = new GridPane();
final Scene scene = new Scene(grid);
grid.setStyle("-fx-background-color:black");
grid.setPrefWidth(400);
grid.setPrefHeight(400);
// get your images from 'aDirectory' and add them to your grid pane.
stage.setScene(scene);
// set the new windows Modality.
stage.initModality(Modality.APPLICATION_MODAL);
stage.show();
}
}
This way you would only need the one button and the dialog would show as soon as you've selected a directory. However, if you would still want a Search and Show button then just store the directory as a variable and add a listener on the 'show' button and move the openNewModalStage call to that one and remove the second argument.
Edit:
Also, depending on how many images and exactly what you want to display in the modal window, you might want to reconsider the GridPane and use a TilePane, or an hbox/vbox inside of a scroll pane. It's just a thought but I don't know what you will be doing with the GridPane.

How to pass file path from a FileChooser button to another button?

I'm trying to make a program with some filters of image by using JavaFx, so I need two button at least, one is a file chooser to open an image, and another one will be a choice box that allows to choose a filter.
My problem is how could a choice box get the path name or a file object from the file chooser.
here is my program unfinished :
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.layout.*;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class Filter extends Application{
public void start(final Stage stage) {
stage.setTitle("FilterShop");
final FileChooser fileChooser = new FileChooser();
final Button openButton = new Button("Select a photo");
ChoiceBox<String> choiceBox = new ChoiceBox<>();
choiceBox.getItems().add("Choose a Filter");
choiceBox.getItems().addAll("Remove watermark", "Brightness", "Grey", "Mosaic");
choiceBox.getSelectionModel().selectFirst();
final Pane stac = new Pane();
openButton.setOnAction(e -> {
File file = fileChooser.showOpenDialog(stage);
if (file != null) {
Image image = new Image(file.toURI().toString());
ImageView imageView = new ImageView(image);
imageView.setX(50);
imageView.setY(50);
imageView.setFitWidth(300);
imageView.setFitHeight(470);
imageView.setPreserveRatio(true);
stac.getChildren().add(imageView);
}
});
choiceBox.setOnAction(event1 -> {
if (choiceBox.getValue() == "Mosaic") {
try {
BufferedImage imagen = ImageIO.read(/* A file object is needed here. */ );
new Mosaic().mosaico(imagen, 80, 80);
} catch (IOException ie) {
System.err.println("I/O Error");
ie.printStackTrace(System.err);
}
}
});
openButton.setLayoutX(300);
openButton.setLayoutY(350);
choiceBox.setLayoutX(430);
choiceBox.setLayoutY(350);
stac.getChildren().addAll(openButton, choiceBox);
stage.setScene(new Scene(stac, 800, 400));
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
I am not sure what is the exact issue you are facing. Firstly FileChooser is not a Button. It is a helper class to interact with ToolKit to open the OS specific file chooser and return the results. And obvisously, it will no keep record of the returned results.
It is your duty to keep a reference of the retrieved file. This can be done in "N" number of ways. As you question focuses on get getting the value from the open button, I would suggest the below approach.
openButton.setOnAction(e -> {
File file = fileChooser.showOpenDialog(stage);
openButton.getProperties().put("FILE_LOCATION", file.getAbsolutePath());
...
});
choiceBox.setOnAction(event1 -> {
if (choiceBox.getValue() == "Mosaic") {
File file = new File(openButton.getProperties().get("FILE_LOCATION").toString());
}
});

How to change ImageView on KeyPressed

I am having difficulty trying to solve this, I am currently trying to figure out how to change the ImageView to another ImageView when I press a certain Key on my keyboard but I have no idea how to, I have already looked around almost everywhere I can think of but haven't got a clue.
Heres the code, this is a character class:
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import javafx.animation.Animation;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Character extends Application{
private static final int COLUMNS = 3;
private static final int COUNT = 3;
private static final int WIDTH = 48;
private static final int HEIGHT = 50;
ImageView imgView;
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
imgView = characterStill();
root.setOnKeyPressed(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
try {
imgView = characterWalking();
} catch (IOException ex) {}
}
});
root.setOnKeyReleased(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
try {
imgView = characterStill();
} catch (IOException ex) {}
}
});
root.getChildren().add(imgView);
Scene scene = new Scene(root, Menu.WIDTH, Menu.HEIGHT, Color.GREENYELLOW);
primaryStage.setScene(scene);
primaryStage.show();
}
public ImageView characterStill() throws IOException{
InputStream is = Files.newInputStream(Paths.get("C:\\Users\\Javier\\Desktop\\StickHero\\StillNinja.png"));
Image characterStill = new Image(is);
ImageView stillView = new ImageView(characterStill);
return stillView;
}
public ImageView characterWalking() throws IOException{
final InputStream is = Files.newInputStream(Paths.get("C:\\Users\\Javier\\Desktop\\StickHero\\WalkingNinja.png"));
final Image characterWalking = new Image(is);
ImageView charView = new ImageView(characterWalking);
charView.setViewport(new Rectangle2D(0, 0, WIDTH, HEIGHT));
final Animation animation = new AnimationGen(charView, Duration.millis(300), COUNT, COLUMNS, 0, 0, WIDTH, HEIGHT);
animation.setCycleCount(Animation.INDEFINITE);
animation.play();
return charView;
}
}
In Java, all objects are accessed via a reference. It is important to keep the distinction between the object in memory, and the reference to the object, clear.
When you execute
new ImageView(characterStill);
a new ImageView object is created in memory. When you then (effectively) do
imgView = new ImageView(characterStill);
you assign the variable imgView a reference to that object. You can think of the reference as the memory location of the object (though in reality it doesn't necessarily have to be implemented that way).
What happens in your code at initialization is effectively the following:
imgView = new ImageView(characterStill);
root.getChildren().add(imgView);
So imgView contains a reference to an ImageView object displaying your characterStill image. You then pass that reference to the child list of root, so now the child list of root contains a reference to that same ImageView object.
Now, when the user presses the RIGHT key, you
(effectively) execute
imgView = new ImageView(characterWalking);
This creates a new ImageView object in memory, and assigns a reference to it (think: memory location of this new object) to the variable imgView.
However, the child list of root hasn't changed: it still contains the original reference to the first ImageView object. So nothing changes in your UI.
You could fix this by replacing the child list of root with the newly created ImageView, i.e.:
root.setOnKeyPressed(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
try {
imgView = characterWalking();
root.getChildren().setAll(imgView);
} catch (IOException ex) {}
}
});
However, this is not very efficient. On every key press, you create a brand new object in memory, from an image freshly loaded from the hard drive. Then you discard the previous object and put the new object in the UI.
A better way is just to use a single ImageView object, and replace the image which it displays. You can do this by calling
imgView.setImage(...);
To make things even more efficient, instead of loading the image from disk every time, you could just load the images at startup and then reuse them. This code looks like
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import javafx.animation.Animation;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Character extends Application{
private static final int COLUMNS = 3;
private static final int COUNT = 3;
private static final int WIDTH = 48;
private static final int HEIGHT = 50;
private ImageView imgView;
private Image characterStill ;
private Image characterWalking ;
private Animation animation ;
public void start(Stage primaryStage) throws Exception {
imgView = new ImageView();
characterStill = loadCharacterStill();
characterWalking = loadCharacterWalking();
imgView.setViewport(new Rectangle2D(0, 0, WIDTH, HEIGHT));
animation = new AnimationGen(charView, Duration.millis(300), COUNT, COLUMNS, 0, 0, WIDTH, HEIGHT);
animation.setCycleCount(Animation.INDEFINITE);
imgView.setImage(characterStill);
Group root = new Group();
root.setOnKeyPressed(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
imgView.setImage(characterWalking);
animation.play();
}
});
root.setOnKeyReleased(e -> {
if(e.getCode().equals(KeyCode.RIGHT)){
imgView.setImage(characterStill);
animation.stop();
}
});
root.getChildren().add(imgView);
Scene scene = new Scene(root, Menu.WIDTH, Menu.HEIGHT, Color.GREENYELLOW);
primaryStage.setScene(scene);
primaryStage.show();
}
public Image loadCharacterStill() throws IOException{
InputStream is = Files.newInputStream(Paths.get("C:\\Users\\Javier\\Desktop\\StickHero\\StillNinja.png"));
Image characterStill = new Image(is);
return characterStill ;
}
public Image loadCharacterWalking() throws IOException{
final InputStream is = Files.newInputStream(Paths.get("C:\\Users\\Javier\\Desktop\\StickHero\\WalkingNinja.png"));
final Image characterWalking = new Image(is);
return characterWalking ;
}
}
Obviously, since I don't have access to your images or your AnimationGen class, I haven't tested this; it should give you the idea though.

Set Graphic to label

I want to set Label to graphic. I tested this code:
private static final ImageView livePerformIcon;
static
{
livePerformIcon = new ImageView(MainApp.class.getResource("/images/Flex.jpg").toExternalForm());
}
final Label label = new Label();
label.setStyle("-fx-background-image: url(\"/images/Flex.jpg\");");
livePerformIcon.setFitHeight(20);
livePerformIcon.setFitWidth(20);
label.setGraphic(livePerformIcon);
But I don't see any image.
The only way that I found to make it work is this:
label.setStyle("-fx-background-image: url(\"/images/Flex.jpg\");");
Is there a way to solve this?
Not sure, but AFAIK controls should be created on the JavaFX Application thread, but you're creating ImageView in a static initializer, which I'm not sure if it's executed on the Application thread.
Besides: Do you really want livePerformIcon to be static???
This one made from the data used in the docs, works perfectly for me
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class LabelWithImages extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Label With Image Sample");
stage.setWidth(400);
stage.setHeight(180);
HBox hbox = new HBox();
//Replace the image you want to put up
Image image = new Image(getClass().getResourceAsStream("a.png"));
Label label = new Label("Demo Label");
label.setGraphic(new ImageView(image));
hbox.setSpacing(10);
hbox.getChildren().add((label));
((Group) scene.getRoot()).getChildren().add(hbox);
stage.setScene(scene);
stage.show();
}
}
Code snippets below will set the value of the property graphic of a label. You can use any of the two. I prefer using javafx css, just to implement the model-view-controller design.
// programmatically, provided with image input stream
label.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("path/to/image.png"))));
// javafx css, provided with image url
.label {
-fx-graphic: url("path/to/image.png");
}

Resources