2

When I create JButtons I get a very big delay. Here is some sample code of how I create my Buttons:

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class Scratch {

public static void main(String[] args) {
    Runnable r = () -> {
        JOptionPane.showMessageDialog(
                null, new Scratch().getUI(new TileSet("Content/Graphics/tileSets/12x12x3 - tileSet.png", 12, 12, 3)));
        JOptionPane.showMessageDialog(
                null, new Scratch().getUI(new TileSet("Content/Graphics/tileSets/16x16x0 - tileSetItems.png", 12, 12, 3)));
        JOptionPane.showMessageDialog(
                null, new Scratch().getUI(new TileSet("Content/Graphics/tileSets/29x18x1 - roguelikeDungeon_transparent.png", 12, 12, 3)));
    };
    SwingUtilities.invokeLater(r);
}

public final JComponent getUI(TileSet tileSet) {
    JPanel ui = new JPanel();
    JPanel tilePanel = new JPanel();
    tilePanel.setLayout(new GridLayout(12, 12, 5, 5));

    long t1 = System.currentTimeMillis();

    TileButton tileMenuButtons[] = new TileButton[tileSet.tileSet.length];
    long tot = 0;
    for (int i = 0; i < tileMenuButtons.length; i++) {
        long t2 = System.currentTimeMillis();
        tileMenuButtons[i] = new TileButton(i,tileSet);
        long t3 = System.currentTimeMillis();
        tot += (t3-t2);
        System.out.println(String.format("It took : "+ tot +"ms for loading "+i+ ". Button "));
        tilePanel.add(tileMenuButtons[i]);
    }

    long t4 = System.currentTimeMillis();

    JScrollPane scrollPane = new JScrollPane();
    scrollPane.getVerticalScrollBar().setUnitIncrement(16);
    scrollPane.setOpaque(true);
    scrollPane.setViewportView(tilePanel);

    ui.add(scrollPane);
    System.out.println(String.format("It took in total : "+ (t4-t1) +"ms for loading "+tileMenuButtons.length+ " TileButtons"));
    return ui;
}

The Out Print gave me following result:

  It took in total : 9661ms for loading the TileSet (144 Buttons)
  It took in total : 13806ms for loading the TileSet (256 Buttons)
  It took in total : 27745ms for loading the TileSet (522 Buttons)

After measuring the time for creating each Button the whole delay is caused by the Buttons:

  It took 30915ms for loading the 521st Button 
  It took in total : 30979ms for loading the TileSet

I could filter that the problem is caused by my Button Class, but I don´t understand where and why ?

  class TileButton extends JButton {
    private int id;
    private TileSet ts = new TileSet("Content/Graphics/tileSets/12x12x3 - tileSet.png", 12, 12, 3);
    private int size = 50;
    public TileButton(int id, TileSet tileSet) {
        super();
        this.ts = tileSet;
        this.id = id;
        loadImage(id);
    }


    public void loadImage(int imageno) {
        this.setBorder(null);
        try {
            Image img = ts.tileSet[imageno].tileImage;
            img = img.getScaledInstance(size, size, Image.SCALE_SMOOTH);
            ImageIcon icon = new ImageIcon(img);
            this.setIcon(icon);
        } catch (Exception e) {
            System.out.println("Fehler beim Laden von Bild");
        }
    }
}

static class TileSet{
    private String tileSetImagePath;
    private int numberOfTilesX, numberOfTilesY;
    private BufferedImage tileSetImage;
    public Tile[] tileSet;
    private int width = Tile.TILEWIDTH, height = Tile.TILEHEIGHT;
    private int border;

    public TileSet(String pTileSetImagePath, int pNumberOfTilesX, int pNumberOfTilesY, int pBorder){
        tileSetImagePath = pTileSetImagePath;
        numberOfTilesX = pNumberOfTilesX;
        numberOfTilesY = pNumberOfTilesY;
        border = pBorder;
        tileSet = new Tile[numberOfTilesX * numberOfTilesY];
        createTileSetImages();
    }

    public void createTileSetImages(){
        try {
            tileSetImage = ImageIO.read(new File(tileSetImagePath));
            width = tileSetImage.getWidth() / numberOfTilesX - border;
            height = tileSetImage.getHeight() / numberOfTilesY - border;
        } catch (IOException e) {
            e.printStackTrace();
        }
        int i = 0;
        for(int y = 0; y < numberOfTilesY; y++) {
            for(int x = 0; x < numberOfTilesX; x++) {
                BufferedImage bi = tileSetImage.getSubimage(x * (width + border), y * (height + border), width, height);
                bi.getScaledInstance(Tile.TILEWIDTH, Tile.TILEHEIGHT, Image.SCALE_SMOOTH);
                tileSet[i++] = new Tile(bi);
            }
        }
    }
}
}
   class Tile extends JPanel{
   public Image tileImage;

   public Tile(Image pTileImage)  {
   super();
   setOpaque(true);
   tileImage = pTileImage;
    }
   }

As Andrew suggested maybe the ScaledInstance causes the delay. Is there any other way to scale an Image wihtout having such a big delay? EDIT: The Scaling doesnt cause the delay : Creating one Button with scaling takes 1ms. (Sorry for the long code but its needed because if I just use (simplified) Icons and Buttons it wouldn´t apply to my problem and therefore wouldn´t help) After trying to create Buttons without ScaledInstance the delay still exists.

Kevin Kumar
  • 71
  • 1
  • 6
  • 1
    1) For better help sooner, [edit] to add a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). 2) One way to get image(s) for an example is to hot link to images seen in [this Q&A](http://stackoverflow.com/q/19209650/418556). E.G. [This answer](https://stackoverflow.com/a/10862262/418556) hot links to an image embedded in [this question](https://stackoverflow.com/q/10861852/418556). 3) See [Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?](http://stackoverflow.com/q/7229226/418556) (Yes.) – Andrew Thompson Apr 18 '19 at 08:53
  • 1
    You probably need to profile your apllication. Usually creating and adding of 100 buttons shouldn't cause such time delay. If you want to get help here, you need to provide [mcve] as it proposed by @AndrewThompson – Sergiy Medvynskyy Apr 18 '19 at 08:55
  • .. 4) *`tileMenuButtons[i].addMouseListener(new MouseListener()`* For a `JButton` use an `ActionListener`. It will respond to both mouse and keyboard input. – Andrew Thompson Apr 18 '19 at 08:55
  • 4
    @SergiyMedvynskyy I couldn't resist [practicing what I preach](https://stackoverflow.com/a/55743116/418556).. – Andrew Thompson Apr 18 '19 at 09:34
  • First of all thank you for all your comments. My project can be found on github : https://github.com/niveKKumar/ChaseMe . I do this all in terms of a school project so i am a very beginner programmer (I will read the article of avoiding setSize methods). @AndrewThompson before i used a JLabel thats why i had a MouseListener but yeah with a button its better to have a Action Listener. I will add the Button class in the post so the code is better to understand. – Kevin Kumar Apr 18 '19 at 15:31
  • 1
    Take Andrew's answer as an inspiration to create your own [mcve] (This isn't your whole code, nor parts of it, it's a brand new program that isolates the problem, i.e. removes the `ActionListeners` if the issue isn't related to it, colors, etc). This way it's easier for all to understand your problem and provide faster and better answers, then you can take that knowledge and apply it on your real project. – Frakcool Apr 18 '19 at 16:07
  • You shouldn;t be creating hundreds of JButtons - Thats the simple answer – gpasch Apr 18 '19 at 17:04
  • @gpasch Actually thats not really an answer or solution. Nobody told you to answer this whole thing is voluntary. If you can´t help then please don´t answer. – Kevin Kumar Apr 18 '19 at 17:32
  • 2
    @KevinKumar, if you can't accept advice (whether you agree with it or not) then don't ask a question. Instead of spending time responding to a comment that you don't find helpful, you could be spending that time creating the [mcve] that you have been asked for. Better yet, why don't you take the time to thank Andrew for the complete code he posted. Why haven't you "accepted" his answer yet? – camickr Apr 18 '19 at 18:49
  • 1
    This part `img.getScaledInstance(size, size, Image.SCALE_SMOOTH);` is likely taking a significant amount of time. Scale the image before it is ever used by the program. But to change 'likely' to 'certainly', post an SSCE / MCVE as I suggested in the first comment. Note that none of the edits so far, and certainly not the entire project on Github, is the form of code I am suggesting. It should be as seen below. A single copy/paste, compile & run to see the GUI (& time it took) on-screen. – Andrew Thompson Apr 19 '19 at 01:36

2 Answers2

5

Here is an MCVE / SSCCE of adding from 100 buttons to 6,400 buttons to a GUI, each with it's own icon.

Typical output here:

It took 14 milliseconds for 100 buttons.
It took 110 milliseconds for 1600 buttons.
It took 138 milliseconds for 6400 buttons.

What it might look like in a frame.

import java.awt.*;
import java.awt.image.*;
import java.io.IOException;
import java.net.*;
import javax.swing.*;
import javax.imageio.*;

public class LotsOfButtons {

    public final JComponent getUI(int pts) {
        JComponent ui = new JPanel(new GridLayout(0, pts));
        try {
            BufferedImage image = ImageIO.read(new URL(
                    "https://i.stack.imgur.com/OVOg3.jpg"));

            int wT = image.getWidth() / pts;
            int hT = image.getHeight() / pts;
            Insets insets = new Insets(0, 0, 0, 0);

            long t1 = System.currentTimeMillis();
            for (int jj = 0; jj < pts; jj++) {
                for (int ii = 0; ii < pts; ii++) {
                    int x = ii * wT;
                    int y = jj * hT;
                    JButton b = new JButton(new ImageIcon(
                            image.getSubimage(x, y, wT, hT)));
                    b.setMargin(insets);
                    ui.add(b);
                }
            }
            long t2 = System.currentTimeMillis();

            System.out.println(String.format(
                    "It took %1s milliseconds for %1s buttons.",
                    (t2 - t1), pts*pts));
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return ui;
    }

    public static void main(String[] args) {
        Runnable r = () -> {
            JOptionPane.showMessageDialog(
                    null, new LotsOfButtons().getUI(10));
            JOptionPane.showMessageDialog(
                    null, new LotsOfButtons().getUI(40));
            JOptionPane.showMessageDialog(
                    null, new LotsOfButtons().getUI(80));
        };
        SwingUtilities.invokeLater(r);
    }
}

So given 14 milliseconds isn't anywhere near "(>30 sec.)" I'm guessing you do it .. differently. Unless that source (above) helps you solve the problem, I suggest to prepare and post an MCVE / SSCCE that hot-links to an image, just like the above source code did, would be the best move in resolving the issue.

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
2

Your problem is probably in TileButton class:

class TileButton extends JButton {
    private int id;
    private TileSet ts = new TileSet("Content/Graphics/tileSets/12x12x3 - tileSet.png", 12, 12, 3);
    private int size = 50;
    public TileButton(int id, TileSet tileSet) {
        super();
        this.ts = tileSet;
        this.id = id;
        loadImage(id);
    }

For every TileButton you create new TileSet. This tile set reads from file - that can cause considerable delays. Then you ignore this tile set and use tileSet passed in constructor.

So instead you should not create new TileSet each time:

class TileButton extends JButton {
    private int id;
    private final TileSet ts;
    private int size = 50;
    public TileButton(int id, TileSet tileSet) {
        super();
        this.ts = tileSet;
        this.id = id;
        loadImage(id);
    }
Piro
  • 1,367
  • 2
  • 19
  • 40
  • This might actually be a case for declaring a `static BufferedImage tileSetImage = null;` in the `TileButton` class. If it's `null`, load it. If not, proceed as you would if it had already been loaded. But passing the image through the constructor is also valid. Either way I'd suggest an image that does not need to be scaled every time the app. is run, and especially not for every button. – Andrew Thompson Apr 19 '19 at 08:19
  • You are golden :) I created on one side the EditorTileButtons and on the other side normal JButtons. I found that the problem is 100% on the TileButton class. After that i removed the TileSet and instead used just the image from the tileSet Panel. Now there is no more delay it was really caused because of the TileSet which is a always newly created (12x12 JPanels for each Button). Thank you both (@AndrewThompson + @Piro) ! You helped me to learn to debug better and also helped me solving the issue. Have a great day and thanks for your help. . – Kevin Kumar Apr 19 '19 at 14:28
  • And @AndrewThompson , scaling the Picture doesnt cause any/minimal delay. The issue was really because of the TileSet. – Kevin Kumar Apr 19 '19 at 14:29