I am doing an application which user by clicking on an image(ImageView1) can see that in another ImageView2. so I try to get the image of ImageView1 in a variable
BufferedImage img= SwingFXUtils.fromFXImage(ImageView1.getImage(), null);
And then assign that variable to ImageView2
ImageView2.setImage(SwingFXUtils.toFXImage(img, null));
But it seems however, setImage is done successfully, but ImageView2 is not showing anything. anyone can help me for a better solution?
Here is the code example:
Controller. ImageView1
#FXML
private void HandleMousePressedOnImageOne()
{
BufferedImage img= SwingFXUtils.fromFXImage(ImageOne.getImage(), null);
try
{
ImageSelection imgSelection= ImageSelection.getImageSelectionInstance();
imgSelection.SetBufferedImageOne(img);
FXMLLoader loader= new FXMLLoader();
SplitPane p= loader.load(getClass().getResource("ImageSelection.fxml").openStream());
ImageSelectionController imgSelectionController= (ImageSelectionController)loader.getController();
imgSelectionController.HandleImageOne();
System.out.println("Image One has been Pressed!");
}
catch(Exception e)
{
e.printStackTrace();
}
}
Model.ImageSelection
public class ImageSelection {
private BufferedImage bufferedImageOne;
private static ImageSelection imageSelectionInstace= new ImageSelection();
private ImageSelection(){}
public static ImageSelection getImageSelectionInstance()
{
return imageSelectionInstace;
}
public void SetBufferedImageOne(BufferedImage img)
{
this.bufferedImageOne= img;
}
public BufferedImage getBufferedImageOne()
{
return this.bufferedImageOne;
}
}
Controller2.ImageView2
public void HandleImageOne(){
ImageSelection imgSelection= ImageSelection.getImageSelectionInstance();
BufferedImage img= imgSelection.getBufferedImageOne();
ImageOne.setImage(SwingFXUtils.toFXImage(img, null));
}
Your code doesn't work because you are loading a new copy of the UI defined in the FXML file ImageSelection.fxml. You don't display this new copy. When you retrieve the controller from the loader that loads that copy of the UI and call
imgSelectionController.HandleImageOne();
you change the image in the ImageView that is part of that new instance of the UI. Since that instance is not displayed, you don't see any effect from that call.
A better approach, which also avoids your first controller having a dependency on the second controller as in your code, is to create an ObjectProperty<Image> in your model, and observe it from the second controller:
public class ImageSelection {
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
private static ImageSelection imageSelectionInstance= new ImageSelection();
private ImageSelection(){}
public static ImageSelection getImageSelectionInstance() {
return imageSelectionInstance;
}
public ObjectProperty<Image> imageProperty() {
return image ;
}
public final void setImage(Image image) {
imageProperty().set(image);
}
public final Image getImage()
{
return imageProperty().get();
}
}
and in the second controller, do
public class ImageSelectionController {
#FXML
private ImageView imageOne ;
public void initialize() {
ImageSelection.getImageSelectionInstance().imageProperty()
.addListener((obs, oldImage, newImage) -> imageOne.setImage(newImage));
}
// ...
}
Now all the first controller needs to do is set the image in the model:
#FXML
private void handleMousePressedOnImageOne() {
ImageSelection.getImageSelectionInstance().setImage(imageOne.getImage());
}
Note that there's absolutely no need to convert from a JavaFX image to a BufferedImage and back.
I also recommend not using a singleton pattern, for a number of (well-documented; just google "what is wrong with using singletons") reasons. Create a single instance and pass it to each of the controllers, or use a dependency injection framework to manage that for you.
Related
I am new to Java and OOP and got stuck in adding image to tableview column. Code seems to work, I can see the name of the student correct but images are not shown in the column. I am getting this error and could not understand how to make it work:
javafx.scene.control.cell.PropertyValueFactory getCellDataReflectively
WARNING: Can not retrieve property 'picture' in PropertyValueFactory: javafx.scene.control.cell.PropertyValueFactory#5b0da50f with provided class type: class model.StudentModel
java.lang.IllegalStateException: Cannot read from unreadable property picture
StudentModel:
package model;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.image.ImageView;
import java.util.ArrayList;
import java.util.List;
public class StudentModel {
private ImageView picture;
private String name;
private SubjectModel major;
private SubjectModel minor;
private String accountPassword;
public String getAccountPassword()
{
return accountPassword;
}
public List<LectureModel> lectureModelList = new ArrayList<>();
public StudentModel(String name, SubjectModel major, SubjectModel minor, ImageView picture, String accountPassword)
{
this.name = name;
this.major = major;
this.minor = minor;
this.picture = picture;
this.accountPassword = accountPassword;
}
public String getName()
{
return name;
}
public ObservableList<LectureModel> myObservableLectures(){
ObservableList<LectureModel> observableList = FXCollections.observableArrayList(lectureModelList);
return observableList;
}
public ImageView getPhoto(){
return picture;
}
public void setPhoto(ImageView photo)
{
this.picture = photo;
}
}
And Participants Scene which I have the tableview:
public class ParticipantsScene extends Scene {
private final StudentController studentController;
private final ClientApplication clientApplication;
private final TableView<StudentModel> allParticipantsTable;
private final ObservableList<StudentModel> enrolledStudents;
private LectureModel lecture;
public ParticipantsScene(StudentController studentController, ClientApplication application, LectureModel lecture) {
super(new VBox(), 800 ,500);
this.clientApplication = application;
this.studentController = studentController;
this.lecture = lecture;
enrolledStudents=lecture.observeAllParticipants();
TableColumn<StudentModel, String > nameCol = new TableColumn<>("Name");
nameCol.setMinWidth(200);
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
TableColumn<StudentModel, ImageView> picCol = new TableColumn<>("Images");
picCol.setPrefWidth(200);
picCol.setCellValueFactory(new PropertyValueFactory<>("picture"));
allParticipantsTable = new TableView<>();
allParticipantsTable.getColumns().addAll(nameCol,picCol);
allParticipantsTable.setItems(enrolledStudents);
VBox vBox = new VBox(10, allParticipantsTable, createButtonBox());
vBox.setAlignment(Pos.CENTER);
setRoot(vBox);
}
private HBox createButtonBox() {
var backButton = new Button("Back");
backButton.setOnAction(event -> clientApplication.showAllLecturesScene());
var buttonBox = new HBox(10, backButton);
buttonBox.setAlignment(Pos.CENTER);
return buttonBox;
}
}
Also adding Lectures model in case it may helpful:
public class LectureModel {
private String lectureName;
private String lectureHall;
private String subjectName;
private SubjectModel subject;
private TimeSlot timeSlot;
//private Button actionButton1;
//private Button actionButton2;
private List<StudentModel> enrolledStudents = new ArrayList<>();
private String name;
public LectureModel(String lectureName, String lectureHall, SubjectModel subject, TimeSlot timeSlot){
this.lectureName = lectureName;
this.lectureHall = lectureHall;
this.subject = subject;
this.timeSlot = timeSlot;
this.subjectName = this.subject.getSubjectName();
}
public String getLectureName()
{
return lectureName;
}
public String getLectureHall()
{
return lectureHall;
}
public SubjectModel getSubject()
{
return subject;
}
public String getSubjectName()
{
return subjectName;
}
public List<StudentModel> getEnrolledStudents()
{
return enrolledStudents;
}
public ObservableList<StudentModel> observeAllParticipants() {
ObservableList<StudentModel> observableList = FXCollections.observableArrayList(getEnrolledStudents());
return observableList;
}
public TimeSlot getTimeSlot() {
return timeSlot;
}
public void addStudent(StudentModel studentModel){ enrolledStudents.add(studentModel);}
public void removeStudent(StudentModel studentModel)
{
enrolledStudents.remove(studentModel);
};
Appreciate any kind of helps,
Thanks!
You have misnamed the property name used in the PropertyValueFactory.
In general, don't use PropertyValueFactories, instead use a lambda:
Why should I avoid using PropertyValueFactory in JavaFX?
Also, as a general principle, place data in the model, not nodes. For example, instead of an ImageView, store either an Image or a URL to the image in the model. Then use nodes only in the views of the model. For example, to display an image in a table cell, use a cell factory.
An LRU cache can be used for the images if needed (it may not be needed).
Often the images displayed in a table might be smaller than the full-size image, i.e. like a thumbnail. For efficiency, you might want to load images in the background using a sizing image constructor.
If you need help placing and locating your image resources, see:
How do I determine the correct path for FXML files, CSS files, Images, and other resources needed by my JavaFX Application?
Example code
The example in this answer uses some of the principles from the answer text:
Uses a Lambda instead of PropertyValue.
The model for list items is represented as a record using immutable data.
Replace the record with a standard class if you want read/write access to data.
An Image URL is stored as a String in the model rather than as an ImageView node.
A cell factory is used to provide an ImageView node to view the image.
Images are loaded in the background and resized to thumbnail size on loading.
You can skip the thumbnail sizing and use full-size images if your app requires that.
You can load in the foreground if you want the UI to wait until the images are loaded before displaying (not recommended, but for small local images you won't see any difference).
Images are loaded in an LRU cache.
If you don't have a lot of images (e.g. thousands), you could instead store the Image (not the ImageView) directly in the model and use that, removing the LRU cache from the solution.
Though I didn't test it, this solution should scale fine to a table with thousands of rows, each with different images.
The images used in this answer are provided here:
JavaFX: ComboBox with custom cell factory - buggy rendering
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.*;
public class StudentTableViewer extends Application {
public record Student(String last, String first, String avatar) {}
#Override
public void start(Stage stage) {
TableView<Student> table = createTable();
populateTable(table);
VBox layout = new VBox(
10,
table
);
layout.setPadding(new Insets(10));
layout.setPrefSize(340, 360);
layout.setStyle("-fx-font-size:20px; -fx-base: antiquewhite");
stage.setScene(new Scene(layout));
stage.show();
}
private TableView<Student> createTable() {
TableView<Student> table = new TableView<>();
TableColumn<Student, String> lastColumn = new TableColumn<>("Last");
lastColumn.setCellValueFactory(
p -> new ReadOnlyStringWrapper(p.getValue().last()).getReadOnlyProperty()
);
TableColumn<Student, String> firstColumn = new TableColumn<>("First");
firstColumn.setCellValueFactory(
p -> new ReadOnlyStringWrapper(p.getValue().first()).getReadOnlyProperty()
);
TableColumn<Student, String> avatarColumn = new TableColumn<>("Avatar");
avatarColumn.setCellValueFactory(
p -> new ReadOnlyStringWrapper(p.getValue().avatar()).getReadOnlyProperty()
);
avatarColumn.setCellFactory(
p -> new AvatarCell()
);
avatarColumn.setPrefWidth(70);
//noinspection unchecked
table.getColumns().addAll(lastColumn, firstColumn, avatarColumn);
return table;
}
public static class AvatarCell extends TableCell<Student, String> {
private final ImageView imageView = new ImageView();
private final ImageCache imageCache = ImageCache.getInstance();
#Override
protected void updateItem(String url, boolean empty) {
super.updateItem(url, empty);
if (url == null || empty || imageCache.getThumbnail(url) == null) {
imageView.setImage(null);
setGraphic(null);
} else {
imageView.setImage(imageCache.getThumbnail(url));
setGraphic(imageView);
}
}
}
private void populateTable(TableView<Student> table) {
table.getItems().addAll(
new Student("Dragon", "Smaug", "Dragon-icon.png"),
new Student("Snake-eyes", "Shifty", "Medusa-icon.png"),
new Student("Wood", "Solid", "Treant-icon.png"),
new Student("Rainbow", "Magical", "Unicorn-icon.png")
);
}
}
class ImageCache {
private static final int IMAGE_CACHE_SIZE = 10;
private static final int THUMBNAIL_SIZE = 64;
private static final ImageCache instance = new ImageCache();
public static ImageCache getInstance() {
return instance;
}
private final Map<String, Image> imageCache = new LruCache<>(
IMAGE_CACHE_SIZE
);
private final Map<String, Image> thumbnailCache = new LruCache<>(
IMAGE_CACHE_SIZE
);
public Image get(String url) {
if (!imageCache.containsKey(url)) {
imageCache.put(
url,
new Image(
Objects.requireNonNull(
ImageCache.class.getResource(
url
)
).toExternalForm(),
true
)
);
}
return imageCache.get(url);
}
public Image getThumbnail(String url) {
if (!thumbnailCache.containsKey(url)) {
thumbnailCache.put(
url,
new Image(
Objects.requireNonNull(
ImageCache.class.getResource(
url
)
).toExternalForm(),
THUMBNAIL_SIZE,
THUMBNAIL_SIZE,
true,
true,
true
)
);
}
return thumbnailCache.get(url);
}
private static final class LruCache<A, B> extends LinkedHashMap<A, B> {
private final int maxEntries;
public LruCache(final int maxEntries) {
super(maxEntries + 1, 1.0f, true);
this.maxEntries = maxEntries;
}
#Override
protected boolean removeEldestEntry(final Map.Entry<A, B> eldest) {
return super.size() > maxEntries;
}
}
}
Currently, I'm playing around with JavaFX as I'm writing a Snake game for my Java Fundamentals class final project. I'm not that new to creating simple games with animations as I've made a bit of them using PyGame module and SDL in C. Anyway, now I'm quite struggling with understanding the correlations of some objects in JavaFX, especially when combined with SceneBuilder's FXMLs.
I just can't understand how to create an equivalent of gameloop I used to implement in PyGame or SDL. What I want to do with the code below is to enter the gameloop as soon as a new Game object is created and draw the state of the game continuously on the gameCanvas created in the SceneBuilder. I think I can easily manage all the stuff later, but I just can't sort it out how to create a legit bond between the FXML canvas and the gameloop I want to run.
GameController.java
public class GameController implements Initializable, ControlledScreen {
#FXML
private Canvas gameCanvas;
#Override
public void setScreenParent(ScreensController screenPage) {
// THIS IS FOR SCENE MANAGEMENT CONCEPT
}
Game.java
public class Game implements Runnable {
public static final int EASY = 1,
MEDIUM = 2,
HARD = 3;
int difficultyLevel, score = 0;
Snake snake;
Food food;
boolean isRunning = true;
public void setLevelEasy() {
difficultyLevel = EASY;
}
public void setLevelMedium() {
difficultyLevel = MEDIUM;
}
public void setLevelHard() {
difficultyLevel = HARD;
}
#Override
public void run() {
while (isRunning) {
}
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
}
}
You cant use FXML file to create new scene. Use this instead
public class Screen extends Application implements Runnable
{
#Override
public void start ( Stage primaryStage )
{
Pane pane = new Pane ();
Scene scene = new Scene(pane,500,300);
primaryStage.setScene ( scene );
primaryStage.show ();
}
#Override
public void run ()
{
launch ();
}
}
anything you want to add goes to pane element your canvas etc.
and by the way make dificulty enum not int like
enum {easy,medium,hard}
that way nobody can set dificulty level to something does not exist.
I have a small problem. In carrying out the method paintComponent() during the animation I have to constantly update the variable bgImage. But it takes a lot of time, so that the animation slows down.
A block of code with the problem:
public class ProblemClass extends JComponent {
private static final int FRAME_FREQUENCY = 30;
private final Timer animationTimer;
public ProblemClass() {
this.animationTimer = new Timer(1000 / FRAME_FREQUENCY, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint(); // When the animation started is often invoked repaint()
}
});
}
// Other code...
/**
* Start animation from another class
*/
public void startAnimation() {
this.animationTimer.start();
}
#Override
protected void paintComponent(Graphics g) {
// GraphicsUtils.gaussianBlur(...) it's a long-time operation
BufferedImage bgImage = GraphicsUtils.gaussianBlur(AnotherClass.getBgImage());
g2.drawImage(bgImage, 0, 0, null);
// Other code...
}
}
I read on the Internet that I need run long task in parallel thread (SwingWorker), but I have no idea how to do it in my case. How can I solve this problem?
P.S. Sorry for my bad English, it's not my first language.
The best you're going to do is having the image update outside of the paint method, and only redraw whenever a new image is ready. Take your existing code, and add a persistent reference to the image, which gets drawn onto the JComponent each paint method. Then have your animation timer do the Gaussian blur and update your image.
public class ProblemClass extends JComponent {
private static final int FRAME_FREQUENCY = 30;
private final Timer animationTimer;
//persistent reference to the image
private BufferedImage bgImage;
public ProblemClass() {
this.animationTimer = new Timer(1000 / FRAME_FREQUENCY, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
//Update the image on each call before repainting
bgImage = GraphicsUtils.gaussianBlur(AnotherClass.getBgImage());
repaint();
}
});
}
/**
* Start animation from another class
*/
public void startAnimation() {
this.animationTimer.start();
}
#Override
protected void paintComponent(Graphics g2) {
g2.drawImage(bgImage, 0, 0, null);
// Other code...
}
}
There is no generic way to solve this when you generate a new background image every time and then blur it. GaussianBlur is a slow operation, period.
If AnotherClass.getBgImage() delivers images from a predefined set of images, then apply the blur once to each image in the set, problem goes away.
If you create the image in AnotherClass.getBgImage() dynamically, then you may be able to speed it up by changing the image creation to create a blurred image from the start. Depending on what is done to create the image this may or may not be feasible.
If neither of the above works out, you need to investigate other options to produce the blurred image which are faster; there are simpler blurring methods that are generally faster but look somewhat similar to a gaussian.
You see it all boils down to getting rid of calling GaussianBlur repeatedly on the performance critical path.
You should extract logic from painter. Painters are called constrantly and should be executed very fast.
BufferedImage bgImage = GraphicsUtils.gaussianBlur(AnotherClass.getBgImage());
This line has to be executed every time? maybe you could use a field to store the image and the painter could just paitn the image, not applying each time a gaussianBlur.
Try this:
public class ProblemClass extends JComponent {
private static final int FRAME_FREQUENCY = 30;
private final Timer animationTimer;
private final BufferedImage bgImage;
public ProblemClass() {
bgImage = GraphicsUtils.gaussianBlur(AnotherClass.getBgImage());
this.animationTimer = new Timer(1000 / FRAME_FREQUENCY, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint(); // When the animation started is often invoked repaint()
}
});
}
// Other code...
/**
* Start animation from another class
*/
public void startAnimation() {
this.animationTimer.start();
}
#Override
protected void paintComponent(Graphics g2) {
// GraphicsUtils.gaussianBlur(...) it's a long-time operation
g2.drawImage(bgImage, 0, 0, null);
// Other code...
}
}
i made a class as a loader for loading images in background Thread , to add them inside the JavaFX Application ,
my problem is , although of this class works as a Task , but it cause the JavaFX Apllication freeze during images loading , and work back again normally after it finish,
The Loader Class Code :
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class Loader{
private String type = "";
private String url = "";
private ImageView image = new ImageView();
private class LoaderService extends Service{
#Override
protected Task createTask() {
return new LoaderTask();
}
}
private class LoaderTask extends Task{
#Override
protected Object call() throws Exception {
new LoaderClass();
return null;
}
}
private String getType(){
return this.type;
}
private String getUrl(){
return this.url;
}
public Loader type(String type){
this.type = type;
return this;
}
public Loader url(String url){
this.url = url;
return this;
}
public ImageView getImage(){
return this.image;
}
public Loader build(){
new LoaderTask().run();
return this;
}
private class LoaderClass{
public LoaderClass(){
switch(getType()){
case "image":
this.loadImage(getUrl());
break;
}
}
private void loadImage(String url){
try{
getImage().setImage(new Image(url));
}catch(Exception ex){
System.out.println("Ex"+ex.getMessage());
}
}
}
}
Example of calling the loader for images from external class to add them inside the main JavaFX Window :
StackPane Pane = new StackPane();
ImageView Icon1 = new NinjaLoader()
.type("image")
.url("http://localhost/images/1.png")
.build()
.getImage();
ImageView Icon2 = new NinjaLoader()
.type("image")
.url("http://localhost/images/2.png")
.build()
.getImage();
ImageView Icon3 = new NinjaLoader()
.type("image")
.url("http://localhost/images/3.png")
.build()
.getImage();
ImageView Icon4 = new NinjaLoader()
.type("image")
.url("http://localhost/images/4.png")
.build()
.getImage();
Pane.getChildren().addAll(Icon1,Icon2,Icon3,Icon4);
so what is the wrong in my code which cause these freezes ?
Thank you ,
You don't need to manage threading at all to load images in a background thread: the Image constructor has a flag to do this. Just do
ImageView icon1 = new ImageView(new Image("http://localhost/images/1.png", true));
etc.
The issue is that you're running your task in the same thread by calling run method of Runnable:
new LoaderTask().run();
The common practice is to run it in a thread (simple):
Thread th = new Thread(new LoaderTask());
th.setDaemon(true);
th.start();
or to send it to a thread pool (a bit more work). More on this in the related Oracle docs...
UPDATE
If it still freezes:
Make sure you don't do any I/O, i.e. reading files or resources in the FX thread, as this would block UI updates an cause the freeze. FX thread is the thread from which you are able to update your UI properties.
Try loading resources in a separate thread and update Scene Graph as soon as some/all of them are loaded into memory by running Platform.runLater.
I'm working on a SmartGWT project where I'd like my main navigation to be done via a treegrid. The treegrid renders proprerly and its DataSource is functioning appropriately as well. The treegrid is correctly situated to the left of the mainView Canvas.
What I can't seem to figure out is how to switch the contents of the mainView Canvas based on what is selected in the NavigationTree. I've mimicked the functionality I'd like by adding new windows to the existing Canvas, but I can't find an example demonstrating how to clear the canvas entirely and replace it with a new Window.
Am I on the right track here? Can anyone point me at an example that shows roughly what I'm trying to accomplish?
public class NavigationTree extends TreeGrid {
public NavigationTree(Canvas mainView)
{
setDataSource(NavigationDataSource.getInstance());
setAutoFetchData(true);
setShowHeader(false);
addNodeClickHandler(new NavClickHandler(mainView));
}
// Handler for clicking an item on the Navigation Tree.
private class NavClickHandler implements NodeClickHandler
{
private Canvas mainView;
public NavClickHandler(Canvas mainView)
{
super();
this.mainView = mainView;
}
#Override
public void onNodeClick(NodeClickEvent event)
{
Window window = new Window();
window.setWidth(300);
window.setHeight(230);
window.setCanDragReposition(true);
window.setCanDragResize(true);
window.setTitle(event.getNode().getAttribute("name"));
window.addItem(new Label("huzzah!"));
window.setParentElement(mainView);
window.redraw();
}
}
}
You can keep the mainView canvas, clear its children (if any is set) and then set the newly created window as its new child. Something like the following as the body of your click handler:
Window window = new Window();
window.setWidth(300);
window.setHeight(230);
window.setCanDragReposition(true);
window.setCanDragResize(true);
window.setTitle(event.getNode().getAttribute("name"));
window.addItem(new Label("huzzah!"));
for (Canvas child: mainView.getChildren()) {
mainView.removeChild(child);
}
mainView.addChild(window);
I managed to accomplish what I needed with the following change to the event handler code:
public NavClickHandler(UI ui) //UI extends HLayout
{
this.ui = ui;
}
#Override
public void onNodeClick(NodeClickEvent event) {
Window window = new Window();
window.setWidth100();
window.setHeight100();
window.setHeaderControls(HeaderControls.HEADER_LABEL);
window.setTitle(event.getNode().getAttribute("name"));
window.addItem(new Label("Huzzah!"));
ui.setMainView(window);
}
...and the following change to my main UI layout:
public void setMainView(Canvas canvas)
{
mainView.destroy();
mainView = canvas;
addMember(mainView);
this.redraw();
}