I want to create a GridPane (which is nested in a ScrollPane) where I add dynamically cells to the GridPane. Each cell contains a VBox with a BackgroundImage, a few Labels and a Checkbox. The Problem is, that the GridPane can contain several hundreds VBoxes, in my case there are about 300 VBoxes and with this many VBoxes the response time of the Gridpane gets really poor. When I click for instance on a CheckBox it takes a few seconds until the CheckBox is selected/unselected, which makes my program pretty much unusable. Without the BackgroundImage the response time of the GridPane is perfect, so I know that the problem here are the Images
This is my Code to create a VBox:
private VBox createAlbumVBox(Album album) {
VBox container = new VBox();
container.setAlignment(Pos.BOTTOM_LEFT);
CheckBox checkBox = new CheckBox();
Label labelAlbum = new Label(album.getName());
Label labelArtist = new Label(album.getArtistName());
labelAlbum.setStyle("-fx-text-fill: #272727");
labelArtist.setStyle("-fx-text-fill: #272727");
Background background;
if(album.getCover() != null)
{
byte[] coverData = album.getCover();
Image image = new Image(new ByteArrayInputStream(coverData));
BackgroundSize bg = new BackgroundSize(100,100,true,true,true,false);
BackgroundImage backgroundImage = new BackgroundImage(image,BackgroundRepeat.NO_REPEAT,BackgroundRepeat.NO_REPEAT,BackgroundPosition.CENTER,bg);
background = new Background(backgroundImage);
}
else
{
Image image = new Image("/ressources/covers/default-cover.png");
BackgroundSize bg = new BackgroundSize(100,100,true,true,true,false);
BackgroundImage backgroundImage = new BackgroundImage(image,BackgroundRepeat.NO_REPEAT,BackgroundRepeat.NO_REPEAT,BackgroundPosition.CENTER,bg);
background = new Background(backgroundImage);
}
checkBox.setOnMouseClicked(e -> {
if (checkBox.isSelected()) {
album.getTitles().forEach(t -> t.setReadyToSync(true));
} else {
album.getTitles().forEach(t -> t.setReadyToSync(false));
}
});
container.setBackground(background);
HBox hBox = new HBox();
hBox.getChildren().addAll(labelAlbum, labelArtist, checkBox);
hBox.setPrefHeight(30);
hBox.setStyle("-fx-background-color: rgba(255, 255, 255, 0.4)");
container.getChildren().addAll(hBox);
return container;
}
I already tried to use an ImageView instead of a BackgroundImage. Unfortunately the performance with an ImageView is as poor as with a BackgroundImage.
This is not really an answer but more a set of suggestions you can try. It's hard to comment on performance issues without a complete mcve, which would allow the issues to be easily reproduced locally in a minimal application.
Some things you could try are:
Use background loading for your images.
Cache loaded images in a LRU cache.
Use a virtualized control, such as a ControlsFX GridView.
See also some of the performance optimization suggestions in a related answer (some of which may not be applicable to your situation):
What is the best way to display millions of images in Java?
Also, your issue could be in code that you don't show. Your routine is being passed an Album instance which includes album data, including image data in binary form. If you load up your album data and images from a database dynamically, then that process could slow or freeze your application, depending upon how you do it.
Related
I have a Pane that includes label1, label2, label3 and a icon(pane with image). I duplicated that Pane and now i have 10 Pane 5 at left 5 at right and i named every item different for Pane(like left1label1,left2label1,right2label3)but i don't want to use different method for every single Pane.
I tried to create an array of panes but couldn't figure it out. All i want is easy way to manage this Panes. How should i do it?
public void leftPane1Config()
{
leftPane1Label1.setText("Object");
leftPane1Label2.setText("HP:"+ hp);
leftPane1Label3.setText("EXP:"+ exp);
Image img= new Image("directory");
leftPane1Icon.setEffect(new ImageInput(img));
}
public void leftPane2Config()
{
leftPane2Label1.setText("Object");
leftPane2Label2.setText("HP:"+ hp);
leftPane2Label3.setText("EXP:"+ exp);
Image img= new Image("directory");
leftPane2Icon.setEffect(new ImageInput(img));
}
.
.And 8 other
.
I have tried to use something like this (trying to create an hierarchy but i couldn't make it :') ) but i couldn't use setText() so it didn't work.
leftPane1.getChildren().add(leftPane1Label1);
leftPane1.getChildren().get(0).setText??
Then i tried to use something like this
Pane[] leftPane= new Pane[5];
leftPane[0]= new Pane();
and again i couldn't figure it out.
It's not really clear what you're asking. Typically you would just write a method to create the panes and parameterize the parts the vary, in the usual way you do in any kind of programming.
public Pane createPane(String text1, String text2, String text3, URL image) {
Image img = new Image(image.toExternalForm());
// whatever layout you need here
VBox pane = new VBox(
new Label(text1),
new Label(text2),
new Label(text3),
new ImageView(img)
);
// any other stuff you need to do
return pane ;
}
And then of course you just call createPane() with the values you need and add the result to your scene graph however you need.
In libgdx I have a ScrollPane in my application and I can't figure out how to get it to scroll without extra space at the top. Currently, there is a massive space at the top of the pane, and when I scroll down, the text (high scores) slowly fill up the space.
BEFORE SCROLLING:
AFTER SCROLLING:
Is it possible to start without the space (so the pane is completely filled) and when I scroll, I can scroll through the high scores without them moving upwards?
I noticed I can accomplish this by removing the following line.
table.setTransform(true);
But if I do this, I cannot scale or set origin on the table so the text appears too large.
Below is my scrollpane creation method:
private void createScrollPane(ArrayList<String> scrollPaneScores) {
scrollPaneViewport = new FitViewport(800, 480);
stage = new Stage(scrollPaneViewport);
Skin skin = new Skin(Gdx.files.internal("skin/uiskin.json"));
Table scrollableTable = new Table();
scrollableTable.setFillParent(true);
stage.addActor(scrollableTable);
Table table = new Table();
for (String score : scrollPaneScores) {
table.add(new TextButton(score, skin)).row();
}
table.pack();
table.setTransform(true);
//table.setTransform(true); //clipping enabled ... this setting makes scrolling not occur in uniform fashion
table.setOrigin(400, 0);
table.setScale(0.75f);
final ScrollPane scroll = new ScrollPane(table, skin);
//scrollableTable.add(scroll).expand().fill();
scrollableTable.add(scroll).maxHeight(300);
//stage.setDebugAll(true);
//When stage is created, set input processor to be a multiplexer to look at both screen and stage controls
inputMultiplexer.addProcessor(inputProcessorScreen);
inputMultiplexer.addProcessor(stage);
Gdx.input.setInputProcessor(inputMultiplexer);
scrollPaneCreated = true;
}
Remove the setFillParent line. It is an old problem with tables inside scrollpanes.
Try removing the scrollableTable.setFillParent(true);
I am new to JavaFX (been working with swing for a long time) and am trying to work with BorderPane. One would assume BorderPane is similar to BorderLayout but the big difference is the center of BorderPane will expand to fit its contents while BorderLayout will shrink to fit the window.
I am using JFXPanel in a JFrame and have a 3 part interface: A panel on the left (some text), some buttons on the bottom (flow control), and in the center want to have a dynamic panel/pane, that for the most part will be just an imageview. I set it all up and it works fine, but I'm working with camera images here which are way bigger than my monitor. I've tried scaling the images down by binding the imageview width to different things (such as anchor pane, scene size (works, but not properly), etc. The issue I am having is that since borderpane's center panel expands to fit its content, it will expand and never have a proper value I can bind to. I need the image to be fully visible in the window at any size.
Here's the code I've been working with.
protected void setupFXWindow(JFXPanel mainPanel) {
butNext = new Button("Next Step");
butBack = new Button("PreviousStep Step");
butQuit = new Button("Cancel Signature Generation");
butNext.setTooltip(new Tooltip("Next step..."));
butBack.setTooltip(new Tooltip("Previous Step..."));
butQuit.setTooltip(new Tooltip("Quit generating a signature"));
Group root = new Group();
Scene scene = new Scene(root, javafx.scene.paint.Color.ALICEBLUE);
javafx.scene.image.Image fximage = new javafx.scene.image.Image(new File(image.getSourceFilePath()).toURI().toString());
ImageView iv1 = new ImageView();
iv1.setImage(fximage);
iv1.setPreserveRatio(true);
VBox directionsPanel = new VBox();
HBox authorflowPanel = new HBox(); //bottom buttons for next, back, etc.
mainPanel.setScene(scene);
//INSTRUCTIONS
directionsStepLabel = new Text();
directionsLabel = new Text();
setDirectionsText("Directions will be placed here.");
exampleLabel = new Text("Example");
exampleIconLabel = new Text("An example image will be shown here.");
directionsPanel.setPadding(new javafx.geometry.Insets(5, 5, 5, 5));
directionsPanel.getChildren().addAll(directionsStepLabel, directionsLabel, exampleLabel);
authorflowPanel.getChildren().addAll(butBack, butQuit, butNext);
BorderPane bp = new BorderPane();
bp.prefHeightProperty().bind(scene.heightProperty());
bp.prefWidthProperty().bind(scene.widthProperty());
bp.setLeft(directionsPanel);
bp.setBottom(authorflowPanel);
bp.setCenter(iv1);
root.getChildren().add(bp);
}
This code sample doesn't have iv1 (imageview) binded to anything, cause at this point I have no idea what I can bind to that will give me the remaining space in the scene. Since I cannot use the full width or height of the scene, I'm at a loss of what I am supposed to do here.
The code above makes it look like this:
Wrap the ImageView in some kind of Pane (e.g. a StackPane). Then the pane will fill the center region of the border pane and you can bind to its width and height:
ImageView iv1 = new ImageView();
iv1.setImage(fximage);
iv1.setPreserveRatio(true);
StackPane imageContainer = new StackPane(iv1);
iv1.fitWidthProperty().bind(imageContainer.widthProperty());
iv1.fitHeightProperty().bind(imageContainer.heightProperty());
// ...
bp.setCenter(imageContainer);
I want to loop through each object of a list. For each entry I want to create a GUI object that looks like this:
A checkbox on the left
An image in the center
(later) A label on the left
My problem is, that each label has a different length and it looks rather strange if not all pictures are on the same line (as seen vertically). Is there a possibility by either java or css to align the ImageVew in center of the HBox?
imageView.setLayoutX(filterBox.getWidth()/2); didn't do the trick unfortunatly. And no -fx-align: right; or -fx-float: right; seems to be existing.
I included what I have so far.
VBox filtersBox = new VBox();
HBox filterBox;
for(Filter filter : filters.getFilters()){
if(!filter.isComplex()){
filterBox = new HBox();
filterBox.getStyleClass().add("filter");
ImageView imageView = new ImageView();
[image view stuff]
final CheckBox cbox = new CheckBox(filter.getName().toString());
filterBox.getChildren().addAll(cbox, imageView);
filtersBox.getChildren().addAll(filterBox);
}
}
As far as I'm aware, this is impossible.
I see two ways you can achieve this layout, though:
Have all the checkboxes have the same (constant) preferred width. This way your image views should line up.
Use a GridPane, and add rows instead of HBoxes
I am writing a program which among other things takes a folder of images (Typically around 2000 jpeg images) resizes them, and adds them to a timeline of images. The result of this being as follows:
This works fine, however the way I have done this seems very inefficient. The code which processes these images is shown below:
public void setTimeline(Vector<String> imagePaths){
int numberOfImages = imagePaths.size();
JLabel [] TotalImages = new JLabel[numberOfImages];
setGridPanel.setLayout(new GridLayout(1, numberOfImages, 10, 0));
Dimension image = new Dimension(96, 72);
if (imagePaths != null){
for(int i = 0; i <numberOfImages; i++){
TotalImages[i] = new JLabel("");
TotalImages[i].setPreferredSize(image);
ImageIcon tempicon = new ImageIcon(imagePaths.elementAt(i));
Image tempimage = tempicon.getImage();
Image newimg = tempimage.getScaledInstance(96, 72, java.awt.Image.SCALE_SMOOTH);
ImageIcon newIcon = new ImageIcon(newimg);
TotalImages[i].setIcon(newIcon);
setGridPanel.add(TotalImages[i]);
}
}
}
As can be seen, this code loops through each image path, adds it to a label and adds it to the panel - performing exactly as it should with the correct output.
However, the time taken to do this is substantial. Typically around 5 minutes for 2000 images (depending on the machine). I wondered if there is any way I could improve this performance by using different techniques?
Any help is greatly appreciated.
Save your scaled instances and load them direct. Hard drive space is cheap. This won't get around the initial cost of generating the thumbs, but any subsequent appearances will be lightning-fast.
takes a folder of images
with processes by using tempimage.getScaledInstance(96, 72, java.awt.Image.SCALE_SMOOTH);
use JTable, with reduced funcionality you can use JList too
Typically around 5 minutes for 2000 images
Image.getScaledInstance is simple asynchonous, witouth guarantee an fast and performance, then you have to redirect loading of images to the Background task
advantage first part of images are available immediatelly
dis_advantage required dispalying statuses of loading for user, very good knowledge about Swing and Event Dispatch Thread
I'd suggest to look at Runnable#Thread, and output put to the DefaultTableModel, notice this output must be wrapped into invokeLater
another and most complex way is use SwingWorker, but required very good knowledge about Java and Swing too
To add to mKorbel's excellent answer, I would definitely use a background thread such as a SwingWorker. This may not make the program any faster, but it will seem a lot faster, and that can make all the difference. Something like:
// use List<String> not Vector<String> so you can use Vector now, or change your
// mind and use ArrayList later if desired
// pass dimensions and components in as parameters to be cleaner
public void setTimeLine2(List<String> imagePaths, Dimension imgSize,
JComponent imgDisplayer) {
if (imagePaths != null && imgSize != null && imgDisplayer != null) {
// are you sure you want to set the layout in here?
imgDisplayer.setLayout(new GridLayout(1, 0, 10, 0));
// create your SwingWorker, passing in parameters that it will need
ImageWorker imgWorker = new ImageWorker(imagePaths, imgSize,
imgDisplayer);
imgWorker.execute(); // then ask it to run doInBackground on a background thread
} else {
// TODO: throw exception
}
}
private class ImageWorker extends SwingWorker<Void, ImageIcon> {
private List<String> imagePaths;
private JComponent imgDisplayer;
private int imgWidth;
private int imgHeight;
public ImageWorker(List<String> imagePaths, Dimension imgSize,
JComponent imgDisplayer) {
this.imagePaths = imagePaths;
this.imgDisplayer = imgDisplayer;
imgWidth = imgSize.width;
imgHeight = imgSize.height;
}
// do image creation in a background thread so as not to lock the Swing event thread
#Override
protected Void doInBackground() throws Exception {
for (String imagePath : imagePaths) {
BufferedImage bImg = ImageIO.read(new File(imagePath));
Image scaledImg = bImg.getScaledInstance(imgWidth, imgHeight,
Image.SCALE_SMOOTH);
ImageIcon icon = new ImageIcon(scaledImg);
publish(icon);
}
return null;
}
// but do all Swing manipulation on the event thread
#Override
protected void process(List<ImageIcon> chunks) {
for (ImageIcon icon : chunks) {
JLabel label = new JLabel(icon);
imgDisplayer.add(label);
}
}
}
Use tiles. Which means than rather than operating on images which are not shown in the screen, you only operated when the image has to be shown on the screen.
You need to maintain the logical position of the timeline, as well as displayed images.
When the user move the cursor to a previously hidden position, you compute which image(s) need to be shown next. If the images are not already processed, you process them. That's the same technique web-browsers use for performance.
A first thing you could do would be to add the images asynchronously, instead of trying to add all of them at once. Loop over them as you do, add them to the panel and render it every few images or so the user doesn't need to wait for a long initialization time.
Reuse image objects. A flyweight pattern would come to mind, and possibly limit the screen redraws to only the portions where you add a new image in your asynchronous loading.
If you are likely to have the same images redrawn (or to reload the same folders) in the future, you might want to consider caching some of the image objects, and maybe to save to file the resized thumbnails (many photo viewers do this and will store thumbnails versions - and some useful metadata - in hidden files or folders, so they can reload them faster the next time around.
what you could do to make it faster is by making 4 threads, and have them computing simultaneously the images. i dont know if the vm will spread them over multiple cpu cores though. something to look into because that would boost perfotrmace on a multicore pc