0

I'm having trouble to find the right Layout Manager. I have some images inside a JPanel, they are all different size so I wanted to use a Flow Layout to let the manager handle them. The manager fills all the first row as possible and then warps to another line. This is all ok, but what I want is to stop at second "warp". I just want to show 2 rows of images and if then you want to see more you must click a JButton to load the others. Actually the FlowLayout keeps inserting in a third line and the images are cutted by half because the panel is not "tall" enough. Any tips? I've already tried with Flow Layout and Mig Layout without success.

Umuril Lyerood
  • 175
  • 1
  • 1
  • 9
  • GridBag is a nice one that I've used. – Logan Feb 25 '16 at 17:57
  • You have to determine how many images you want to show, like 3 images in a row and two rows on a JPanel, and display only that amount. The FlowLayout works, the GridLayout might look better. – Gilbert Le Blanc Feb 25 '16 at 17:58
  • not showing overflow components is unusual - if nothing works you can always implement your own LayoutManager – wero Feb 25 '16 at 18:08
  • `GridBag` and `GridLayout` will be helpful. – ifly6 Feb 25 '16 at 18:10
  • Grid/GridBag layouts afaik are even worse. If I have a row of long horizontal images and the other with vertical the output should be ugly. @wero I think I'll try what you said. Do you recommend to implement a LayoutManager or extend the FlowLayout for this problem? (create an answer so I can upvote you) – Umuril Lyerood Feb 25 '16 at 19:30
  • @UmurilLyerood it is probably easier to only get inspiration from FlowLayout's source but who knows. Glad to help and happy coding – wero Feb 25 '16 at 19:42
  • You mean something like [this](http://stackoverflow.com/questions/15961412/add-thumnails-to-spring-layout-like-a-grid/15961424#15961424) or [this](http://stackoverflow.com/questions/18010803/java-swing-resize-imageicon-according-to-jlabel/18011430#18011430) – MadProgrammer Feb 25 '16 at 21:44
  • @MadProgrammer none of those. They have equal width or height the images. It took me a while to find an example but I think something like what google images does. I just want the first two rows without any JScroolPane. And organized as google does. Without any grid but the images fill rows even if they have different size. (Sorry I don't know how to explain it better) [Google Images Example](https://www.google.com/search?q=black+screen&tbm=isch&tbo=u&source=univ&sa=X&ved=0ahUKEwiktaX7gZTLAhVJtBQKHevKBe0QsAQIHA&biw=1440&bih=739) – Umuril Lyerood Feb 25 '16 at 22:59
  • But the first example doesn't have "equal" size images, while I'm not 100% (it's been a while since I ran it) neither does the second one. Each row will adapt to the height of the highest image, they both use a `WrapLayout` which is an extension of `FlowLayout` that, well, allows the components to wrap based on the available width of the container – MadProgrammer Feb 25 '16 at 23:08

2 Answers2

0

Combine a FlowLayout(outerPanel) with a GridBagLayout(innerPannel) u can define the rows by yourself, and then u put a JScrollPane over it so it wont cut your pictures and u will still be able to see them in full size(my suggestion)

Marcel
  • 1,509
  • 1
  • 17
  • 39
  • I'm not sure if I understand you well, but the problem in your implementation is to know when I have to warp to the next row. I don't know the size of the images(before), so I can't know when the row is full. Of course I could get the width of each image, and calculate it all. But I was looking for a layout manager that could handle that for me. – Umuril Lyerood Feb 25 '16 at 19:34
0

I couldn't resist trying to solve this. I had hoped to make use of FlowLayouts, but in the end, I ended up using only GridBagLayouts: One for each row of images, plus one for the entire container. I may have over-engineered in terms of flexibility, but I imagine something like this may be useful to myself or others in the future.

In essence, every time the container changes size, or images are added/removed, or any of its visual properties change, updateLayout() is called, which rebuilds all of the GridBagLayout panels from scratch. I'm sure there are ways to make this more efficient, and I'm sure it's possible to write a LayoutManager2 implementation from scatch that does the job, but this performed reasonably well for me.

import java.util.List;
import java.util.ArrayList;
import java.util.Objects;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;

import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import java.awt.image.BufferedImage;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class ImagePanel
extends JPanel {
    private static final long serialVersionUID = 1;

    /** @serial */
    private final List<Icon> images = new ArrayList<>();

    /** @serial */
    private final List<Icon> scaledImages = new ArrayList<>();

    /** @serial */
    private int maxRowCount = 2;

    /** @serial */
    private int hgap = 6;

    /** @serial */
    private int vgap = 6;

    /** @serial */
    private int maxImageWidth = 200;

    /** @serial */
    private int maxImageHeight = 200;

    public ImagePanel() {
        setLayout(new GridBagLayout());
        addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent event) {
                updateLayout();
            }
        });
    }

    @Override
    public void addNotify() {
        super.addNotify();
        updateLayout();
    }

    @Override
    public Dimension getPreferredSize() {
        Rectangle screen = findGraphicsConfiguration().getBounds();

        Dimension size = new Dimension();
        Dimension row = new Dimension();
        int rowsComputed = 0;
        int gap = 0;
        for (Icon image : scaledImages) {
            if (row.width > 0 &&
                row.width + gap + image.getIconWidth() > screen.width) {

                if (++rowsComputed >= maxRowCount) {
                    break;
                }

                size.width = Math.max(size.width, row.width);
                size.height += (size.height > 0 ? vgap : 0) + row.height;
                row.setSize(0, 0);
                gap = 0;
            }
            row.width += gap + image.getIconWidth();
            row.height = Math.max(row.height, image.getIconHeight());
            gap = hgap;
        }

        size.width = Math.max(size.width, row.width);
        size.height += (size.height > 0 ? vgap : 0) + row.height;

        return size;
    }

    private void updateLayout() {
        int width = getWidth();
        if (width == 0) {
            return;
        }

        for (Component rowContainer : getComponents()) {
            ((JComponent) rowContainer).removeAll();
        }

        GridBagConstraints rowConstraints = new GridBagConstraints();
        rowConstraints.gridwidth = GridBagConstraints.REMAINDER;
        rowConstraints.weightx = 1;
        rowConstraints.anchor = GridBagConstraints.FIRST_LINE_START;

        int row = -1;
        int rowWidth = 0;
        GridBagConstraints gbc = new GridBagConstraints();
        for (Icon image : scaledImages) {
            JComponent rowContainer = (row >= 0 && row < getComponentCount() ?
                (JComponent) getComponent(row) : null);
            int gap = (rowWidth > 0 ? hgap : 0);

            if (rowContainer == null ||
                rowWidth + gap + image.getIconWidth() > width) {

                if (++row >= maxRowCount) {
                    break;
                }

                gap = 0;
                rowWidth = 0;

                if (row < getComponentCount()) {
                    rowContainer = (JComponent) getComponent(row);
                } else {
                    rowContainer = new JPanel(new GridBagLayout());
                    add(rowContainer, rowConstraints);
                }
                rowConstraints.insets.top = vgap;
            }

            gbc.insets.left = gap;
            JComponent imageContainer = new JLabel(image);
            rowContainer.add(imageContainer, gbc);

            rowWidth += gap + image.getIconWidth();
        }

        for (int i = getComponentCount() - 1; i >= maxRowCount; i--) {
            remove(i);
        }
    }

    private GraphicsConfiguration findGraphicsConfiguration() {
        GraphicsConfiguration config = getGraphicsConfiguration();
        if (config == null) {
            GraphicsEnvironment env =
                GraphicsEnvironment.getLocalGraphicsEnvironment();
            config = env.getDefaultScreenDevice().getDefaultConfiguration();
        }
        return config;
    }

    private Icon scale(Icon image) {
        int imageWidth = image.getIconWidth();
        int imageHeight = image.getIconHeight();
        if (imageWidth > maxImageWidth || imageHeight > maxImageHeight) {
            float scale = Math.min((float) maxImageWidth / imageWidth,
                                   (float) maxImageHeight / imageHeight);
            if (scale < 1) {
                GraphicsConfiguration config = findGraphicsConfiguration();
                BufferedImage scaledImage = config.createCompatibleImage(
                    (int) (imageWidth * scale),
                    (int) (imageHeight * scale));
                Graphics2D g = scaledImage.createGraphics();
                g.scale(scale, scale);
                image.paintIcon(this, g, 0, 0);
                g.dispose();

                image = new ImageIcon(scaledImage);
            }
        }

        return image;
    }

    public List<Icon> getImages() {
        return new ArrayList<>(images);
    }

    public void clearImages() {
        images.clear();
        updateLayout();
        revalidate();
    }

    public void addImage(Icon image) {
        Objects.requireNonNull(image, "Image cannot be null");
        images.add(image);
        scaledImages.add(scale(image));
        updateLayout();
        revalidate();
    }

    public void removeImage(Icon image) {
        int index = images.indexOf(image);
        if (index >= 0) {
            removeImage(index);
        }
    }

    public void removeImage(int index) {
        images.remove(index);
        scaledImages.remove(index);
        updateLayout();
        revalidate();
    }

    public int getHgap() {
        return hgap;
    }

    public void setHgap(int gap) {
        if (gap < 0) {
            throw new IllegalArgumentException("Gap must be at least zero");
        }

        int old = this.hgap;
        this.hgap = gap;

        if (old != gap) {
            updateLayout();
            revalidate();
        }

        firePropertyChange("hgap", old, gap);
    }

    public int getVgap() {
        return vgap;
    }

    public void setVgap(int gap) {
        if (gap < 0) {
            throw new IllegalArgumentException("Gap must be at least zero");
        }

        int old = this.vgap;
        this.vgap = gap;

        if (old != gap) {
            updateLayout();
            revalidate();
        }

        firePropertyChange("vgap", old, gap);
    }

    public int getMaxRowCount() {
        return maxRowCount;
    }

    public void setMaxRowCount(int count) {
        if (count < 0) {
            throw new IllegalArgumentException("Count must be at least zero");
        }

        int old = this.maxRowCount;
        this.maxRowCount = count;

        if (old != count) {
            updateLayout();
            revalidate();
        }

        firePropertyChange("maxRowCount", old, count);
    }

    public int getMaxImageWidth() {
        return maxImageWidth;
    }

    private void recomputeScaledImages() {
        scaledImages.clear();
        for (Icon image : images) {
            scaledImages.add(scale(image));
        }
    }

    public void setMaxImageWidth(int width) {
        if (width <= 0) {
            throw new IllegalArgumentException("Width must be positive");
        }

        int old = this.maxImageWidth;
        this.maxImageWidth = width;

        if (old != width) {
            recomputeScaledImages();
            updateLayout();
            revalidate();
        }

        firePropertyChange("maxImageWidth", old, width);
    }

    public int getMaxImageHeight() {
        return maxImageHeight;
    }

    public void setMaxImageHeight(int height) {
        if (height <= 0) {
            throw new IllegalArgumentException("Height must be positive");
        }

        int old = this.maxImageHeight;
        this.maxImageHeight = height;

        if (old != height) {
            recomputeScaledImages();
            updateLayout();
            revalidate();
        }

        firePropertyChange("maxImageHeight", old, height);
    }

    public static void main(final String[] args)
    throws java.io.IOException {
        if (args.length == 0) {
            System.err.println("Usage: java " + ImagePanel.class.getName()
                + " <directory> | <url1> <url2> ...");
            System.exit(2);
        }

        final List<java.net.URL> urls;
        if (args.length == 1 && !args[0].contains(":")) {
            urls = new ArrayList<>();
            try (java.nio.file.DirectoryStream<java.nio.file.Path> dir =
                java.nio.file.Files.newDirectoryStream(
                    java.nio.file.Paths.get(args[0]))) {

                for (java.nio.file.Path file : dir) {
                    urls.add(file.toUri().toURL());
                }
            }
        } else {
            urls = new ArrayList<>(args.length);
            for (String url : args) {
                urls.add(new java.net.URL(url));
            }
        }

        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ImagePanel imagePanel = new ImagePanel();
                for (java.net.URL url : urls) {
                    imagePanel.addImage(new ImageIcon(url));
                }

                javax.swing.JFrame frame =
                    new javax.swing.JFrame("ImagePanel");
                frame.setDefaultCloseOperation(
                    javax.swing.JFrame.EXIT_ON_CLOSE);

                JPanel panel = new JPanel(new GridBagLayout());
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.fill = GridBagConstraints.HORIZONTAL;
                gbc.anchor = GridBagConstraints.FIRST_LINE_START;
                gbc.weightx = 1;
                gbc.weighty = 1;
                panel.add(imagePanel, gbc);

                frame.getContentPane().add(panel);
                frame.pack();
                frame.setLocationByPlatform(true);
                frame.setVisible(true);
            }
        });
    }
}
VGR
  • 40,506
  • 4
  • 48
  • 63
  • I'll read the code tomorrow morning. But i've tried it a couple of times, it resizes my images and fills a grid with them as far as I've seen. All the images are the same width so there is no need to use a FlowLayout, is that intended to? or it's me that I did something wrong? I'd be pleasure if you could contact me by mail to get more info about code and reach the solution. umuril.lyerood@gmail.com – Umuril Lyerood Feb 29 '16 at 23:20
  • Yes, I was testing with images of diverse sizes, so I added maxImageWidth and maxImageHeight properties. You can easily remove that by removing the `scaledImages` field and removing all references to it in the code, except in the loops in `getPreferredSize` and `updateLayout`, where you can simply replace scaledImages with `images`. – VGR Mar 01 '16 at 00:08