将 JPanel 添加到 JList 中?

发布于 2024-11-30 14:51:13 字数 70 浏览 1 评论 0原文

使用 NetBeans GUI 编辑器为学校创建保龄球程序。我可以将 JPanel 添加到 JList 吗?如果是这样怎么办?

Using NetBeans GUI editor to create a bowling program for school. Is it possible for me to add a JPanel to a JList? if so how?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

森林迷了鹿 2024-12-07 14:51:13

这实际上是不可能的(意味着它不会按照您的预期运行) - 您真正想要的是一个列表 LayoutManager ,它将在垂直或水平列表中布局组件。因此,您可以使用带有类似列表的布局管理器的 JPanel,而不是使用 JList

尝试这些:

  • BoxLayout 将放置单个列/行中的所有 JPanel
  • GridLayout 会将所有 JPanels 放在单个列/行中,并使它们的大小相同

It's not really possible (meaning it won't behave as you'd expect) - what you actually want is a list LayoutManager that will lay out the components in a vertical or horizontal list. So instead of using JList, you'd use a JPanel with a list-like layout manager.

Try these:

  • BoxLayout will put all the JPanels in a single column/row
  • GridLayout will put all the JPanels in a single column/row and make them all the same size
待"谢繁草 2024-12-07 14:51:13

试试这个。这对我有用。

class PanelRenderer implements ListCellRenderer {

    @Override
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
        JPanel renderer = (JPanel) value;
        renderer.setBackground(isSelected ? Color.red : list.getBackground());
        return renderer;
    }
}

public void ShowItemList(List<JPanel> paneList, JPanel container) {


        DefaultListModel model = new DefaultListModel();

        for (JPanel pane:paneList) {

                model.addElement(pane);

        }
        final JList list = new JList(model);
        list.setFixedCellHeight(40);
        list.setSelectedIndex(-1);

        list.setCellRenderer(new JPanelToJList.PanelRenderer());
        JScrollPane scroll1 = new JScrollPane(list);
        final JScrollBar scrollBar = scroll1.getVerticalScrollBar();
        scrollBar.addAdjustmentListener(new AdjustmentListener() {
            @Override
            public void adjustmentValueChanged(AdjustmentEvent e) {
                System.out.println("JScrollBar's current value = " + scrollBar.getValue());
            }
        });


        container.add(scroll1);


}

Try This. it's works for me.

class PanelRenderer implements ListCellRenderer {

    @Override
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
        JPanel renderer = (JPanel) value;
        renderer.setBackground(isSelected ? Color.red : list.getBackground());
        return renderer;
    }
}

public void ShowItemList(List<JPanel> paneList, JPanel container) {


        DefaultListModel model = new DefaultListModel();

        for (JPanel pane:paneList) {

                model.addElement(pane);

        }
        final JList list = new JList(model);
        list.setFixedCellHeight(40);
        list.setSelectedIndex(-1);

        list.setCellRenderer(new JPanelToJList.PanelRenderer());
        JScrollPane scroll1 = new JScrollPane(list);
        final JScrollBar scrollBar = scroll1.getVerticalScrollBar();
        scrollBar.addAdjustmentListener(new AdjustmentListener() {
            @Override
            public void adjustmentValueChanged(AdjustmentEvent e) {
                System.out.println("JScrollBar's current value = " + scrollBar.getValue());
            }
        });


        container.add(scroll1);


}
东北女汉子 2024-12-07 14:51:13

转到本教程:
它与您的问题具有相同的概念...

http:// docs.oracle.com/javase/tutorial/uiswing/components/combobox.html#renderer

goto this tutorial:
it has the same concept with your problem...

http://docs.oracle.com/javase/tutorial/uiswing/components/combobox.html#renderer

谁人与我共长歌 2024-12-07 14:51:13

(编辑:我已经使用这个有一段时间了,我很惊讶。迄今为止我见过的最快的列表组件。为什么我以前没有见过这个?)

我刚刚制作了一些不是 JList 的东西,所以它缺少很多功能,但您可以很容易地添加这些功能。

但您得到的是:一个列表(所有成员大小相同。),可以轻松容纳约 20 亿个面板,没有内存或性能问题 - 请参阅演示代码。此外,JPanel 可以包含您想要的任何内容,这些组件将正常工作。

在演示中,JPanel 成员没有内部 JPanel,并且对鼠标事件完全透明(JButton 除外,这很好):添加到整个容器的侦听器接收它们,如演示中所示。如果您添加更多组件层次结构,事情可能会变得棘手,IDK。

不管怎样,这个东西快如闪电,最重要的是,它完成了工作:列表中的 JPanel 既可以操作,也可以选择。 (没有内置选择代码,但就像我说的:很容易做到。鼠标悬停演示代码在里面。)

在此处输入图像描述

演示类:

final public class FastPanelListDemo {


    private static JFrame window = null;
    private static FastPanelList panelList = null;


    public static void main(final String[] args) {

        SwingUtilities.invokeLater(() -> {

            setLookAndFeelDefault();

            panelList = new FastPanelList(FastPanelList.FPLOrientation.VERTICAL,
                                          FastPanelListDemo::supplyPanel,
                                          0.1,
                                          0.95,
                                          false,
                                          80,
                                          Integer.MAX_VALUE);
            final Container contentPane = panelList.container;
            contentPane.setPreferredSize(new Dimension(300, 800));
            contentPane.setBackground(Color.GRAY);

            window = new JFrame("FastPanelList demo");
            window.setContentPane(contentPane);
            window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            window.pack();
            window.setLocationRelativeTo(null);
            window.setVisible(true);


            contentPane.addMouseMotionListener(new MouseAdapter() {

                @Override
                public void mouseMoved(final MouseEvent e) {

                    final JPanel itemUnderMouse = panelList.getItemUnderMouse(e);
                    if (itemUnderMouse != null) {
                        itemUnderMouse.setBackground(new Color((float) Math.random(),
                                                               (float) Math.random(),
                                                               (float) Math.random()));
                    }
                }
            });

        });
    }


    private static JPanel supplyPanel(final int panelIndex) { // Just supply something that extends JPanel. You can put as much data in as you want. E.g. "boolean isMouseHovering" etc.

        final JLabel label = new JLabel("panel " + panelIndex);
        label.setHorizontalAlignment(SwingConstants.CENTER);
        label.setVerticalAlignment(SwingConstants.CENTER);

        final JButton button = new JButton("click me");
        button.addActionListener(e -> {
            JOptionPane.showMessageDialog(window,
                                          "That was button " + panelIndex + ".",
                                          "* CLICK *",
                                          JOptionPane.INFORMATION_MESSAGE);
        });

        final JPanel panel = new JPanel(new BorderLayout(0,
                                                         0));
        panel.setBorder(BorderFactory.createEmptyBorder(10,
                                                        10,
                                                        10,
                                                        10));
        panel.setBackground(new Color((float) Math.random(),
                                      (float) Math.random(),
                                      (float) Math.random()));
        panel.add(label, BorderLayout.CENTER);
        panel.add(button, BorderLayout.EAST);

        return panel;
    }


    private static void setLookAndFeelDefault() {

        setLookAndFeel("Windows",
                       UIManager.getSystemLookAndFeelClassName(),
                       UIManager.getCrossPlatformLookAndFeelClassName(),
                       "Windows Classic",
                       "Nimbus",
                       "Metal",
                       "CDE/Motif");
    }


    /**
     * @param intendedLAFIs ANYTHING, but ideally a LookAndFeel name or several. The first value that equalsIgnoreCase
     *                      an installed LookAndFeelInfo.getName() will be used.
     */
    private static void setLookAndFeel(final String... intendedLAFIs) {

        if (intendedLAFIs != null && intendedLAFIs.length > 0) {
            final UIManager.LookAndFeelInfo[] installedLAFIs = UIManager.getInstalledLookAndFeels();
            LAFILOOP:
            for (String intendedLAFI : intendedLAFIs) {
                for (final UIManager.LookAndFeelInfo lafi : UIManager.getInstalledLookAndFeels()) {
                    if (lafi.getName().equalsIgnoreCase(intendedLAFI)) {
                        try {
                            UIManager.setLookAndFeel(lafi.getClassName());
                            break LAFILOOP;
                        } catch (Exception e) {
                            continue LAFILOOP;
                        }
                    }
                }
            }
        } else {
            throw new IllegalArgumentException("intendedLAFIs is null or empty.");
        }
    }
}

FastPanelList 类:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;




/**
 * FastPanelList v[2pre1, 2019-05-28 10!00 UTC] by Dreamspace President
 */
final public class FastPanelList<T extends JPanel> {


    public enum FPLOrientation {
        HORIZONTAL(Adjustable.HORIZONTAL),
        VERTICAL(Adjustable.VERTICAL);


        final public int orientationAsConstant;


        FPLOrientation(final int orientationAsConstant) {


            this.orientationAsConstant = orientationAsConstant;
        }


    }




    final public FPLOrientation orientation;
    final private Function<Integer, T> panelSupplier;
    final private double fractionOfExtentToScrollPerArrowClick;
    final private double fractionOfExtentToScrollPerTrackClick;
    final private double fractionOfExtentToScrollPerMouseWheelStep;
    final private boolean hideScrollbarWhenUnnecessary;

    final private JScrollBar scrollBar;
    final private int scrollBarWidth; // The default width it normally has in any GUI.
    final public JPanel container; // The container of it all.

    private int panelSize = 0;  // The horizontal or vertical extent of each contained panel.
    private int panelCount = 0; // The amount of panels, indeed max Integer.MAX_VALUE.

    private long contentSize = 0; // The sum total extent of all "contained panels". (They're not really contained, but nobody will see that.)
    private long actualScrollPosition = 0; // The true scroll position, think contentSize.
    private Dimension lastKnownContainerSize = new Dimension(0, 0);


    private Map<Integer, T> knownPanels = new HashMap<>(); // All panels of which some pixels are currently potentially visible are cached here.


    /**
     * @param orientation                           Whether horizontal or the more common vertical arrangement.
     * @param panelSupplier                         Your code that supplies the panels as needed on the fly. The
     *                                              argument will NEVER be null - and your return value, too, must never
     *                                              be null.
     * @param fractionOfExtentToScrollPerArrowClick E.g. 0.1 for 10% of the visible area to become hidden/shown when you
     *                                              click a scrollbar arrow.
     * @param fractionOfExtentToScrollPerTrackClick E.g. 0.95 for 95% of the visible area to become hidden/shown when
     *                                              you click in the scrollbar track.
     * @param hideScrollbarWhenUnnecessary          Guess.
     * @param panelSize                             Can later also be done via setter. (Not tested.) KEEP IN MIND THAT
     *                                              THIS IS NOT YET SCALED, so if you have Desktop scaling 200% and are
     *                                              running Java 8, you need to double the value (e.g. use my GUIScaling
     *                                              class to automate this).
     * @param panelCount                            dto.
     */
    public FastPanelList(final FPLOrientation orientation,
                         final Function<Integer, T> panelSupplier,
                         final double fractionOfExtentToScrollPerArrowClick,
                         final double fractionOfExtentToScrollPerTrackClick,
                         final double fractionOfExtentToScrollPerMouseWheelStep,
                         final boolean hideScrollbarWhenUnnecessary,
                         final int panelSize,
                         final int panelCount) {


        if (orientation == null) {
            throw new IllegalArgumentException("orientation is null.");
        }
        if (panelSupplier == null) {
            throw new IllegalArgumentException("panelSupplier is null.");
        }

        this.orientation = orientation;
        this.panelSupplier = panelSupplier;
        this.fractionOfExtentToScrollPerArrowClick = Math.max(0, fractionOfExtentToScrollPerArrowClick);
        this.fractionOfExtentToScrollPerTrackClick = Math.max(0, fractionOfExtentToScrollPerTrackClick);
        this.fractionOfExtentToScrollPerMouseWheelStep = Math.max(0, fractionOfExtentToScrollPerMouseWheelStep);
        this.hideScrollbarWhenUnnecessary = hideScrollbarWhenUnnecessary;
        setPanelSize(panelSize);
        setPanelCount(panelCount);

        scrollBarWidth = determineScrollBarDefaultWidth();
        scrollBar = new JScrollBar(orientation.orientationAsConstant, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
        scrollBar.addAdjustmentListener(e -> update());

        container = new JPanel(null); // NULL: We want to layout everything manually.
        //        container.add(scrollBar);
        container.addComponentListener(new ComponentAdapter() {

            @Override
            public void componentResized(final ComponentEvent e) {

                update();
            }
        });

        container.addMouseWheelListener(this::mouseWheelEvent);

    }


    public void mouseWheelEvent(final MouseWheelEvent e) {

        final int rotation = e.getWheelRotation();
        final int extent = scrollBar.getModel().getExtent();
        final int increment = (int) Math.max(1, Math.min(extent,
                                                         extent * fractionOfExtentToScrollPerMouseWheelStep));

        scrollBar.setValue(scrollBar.getValue() + (rotation * increment));
    }


    private int determineScrollBarDefaultWidth() { // Called only ONE time.

        final JScrollPane dummyForDefaultSize = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
                                                                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        dummyForDefaultSize.setPreferredSize(new Dimension(1000, 1000));
        dummyForDefaultSize.setSize(dummyForDefaultSize.getPreferredSize());
        dummyForDefaultSize.doLayout();
        return dummyForDefaultSize.getVerticalScrollBar().getSize().width;
    }


    /**
     * FastPanelList requires each item to have the exact same size. This is where you define it (if you reconsidered
     * after your constructor call).
     *
     * @param panelSize Will become >=1
     */
    public void setPanelSize(final int panelSize) {

        this.panelSize = Math.max(1, panelSize);
    }


    /**
     * FastPanelList easily manages Integer.MAX_VALUE (about 2 billion) panels with no memory or performance problems.
     * You define the amount here. You don't add/remove panels in this thing: Instead, you will be asked to provide
     * panels as required depending on screen layout etc.
     *
     * @param panelCount Will become >=0
     */
    public void setPanelCount(final int panelCount) {

        this.panelCount = Math.max(0, panelCount);
    }


    /**
     * Clears the internal JPanel cache. Necessary if you want to repopulate the list. Setting the panel count and
     * calling update() is not sufficient. (Call update AFTER this method.)
     */
    public void clear() {

        knownPanels.clear();
    }


    public T getItemUnderMouse(final MouseEvent e) {

        return getItemUnderMouse(e.getX(), e.getY());
    }


    public T getItemUnderMouse(final int xInComponent,
                               final int yInComponent) {

        final long realPositionUnderMouse = (actualScrollPosition + (orientation == FPLOrientation.HORIZONTAL ? (long) xInComponent : (long) yInComponent));

        final int indexUnderMouse = (int) (realPositionUnderMouse / panelSize);
        return knownPanels.get(indexUnderMouse);
    }


    /**
     * This method is very lenient.
     *
     * @param index                   Anything.
     * @param callSupplierIfNotCached Depends on what you're trying to achieve. E.g. use FALSE if you want to set the
     *                                background color of one of the visible JPanels. The method would return null if
     *                                the panel is not visible, because then it is also no longer cached.
     * @return NULL if index is NULL, or is less than 0, or is equal to or greater than panelCount. Else the cached
     * JPanel (or whatever it secretly is via "extends"), meaning one of the panels that are currently visible or at the
     * edge of visibility. In all other cases, either null will be returned - or your supplier will be called, so you
     * get your own JPanel spat right back at you.
     */
    public T getItem(final Integer index,
                     final boolean callSupplierIfNotCached) {

        T ret = null;
        if (index != null && index >= 0 && index < panelCount) {
            ret = knownPanels.get(index);
            if (ret == null && callSupplierIfNotCached) {
                ret = panelSupplier.apply(index);
                if (ret == null) {
                    throw new IllegalArgumentException("panelSupplier returned null for index " + index);
                }
            }
        }
        return ret;
    }


    /**
     * @return a NEW Map containing the Map entries of the internal knownPanels map. These maps contain all panels that
     * are currently visible on screen. The index is identical to the number handed to your Supplier.
     * <p>
     * The purpose of that internal map is to not request EVERY panel anew every time, but only the panels that are
     * scrolled in at the edge of the screen. Obviously, this is also very useful to you, because you can call this
     * method to get all panels to change their look, data, whatever. And ONLY THOSE panels need to be changed. All
     * others ... don't exist. They only exist in the fantasy of the user. Until they scroll there, then some have
     * become real while others have fallen out of existence.
     */
    public Map<Integer, T> getCachedItems() {

        return new HashMap<>(knownPanels);
    }


    /**
     * This layouts the component. This is done automatically when the scrollbar is moved or the container is resized,
     * but any other action would require YOU to call this.
     */
    public void update() {

        container.removeAll();

        lastKnownContainerSize = container.getSize();

        final int containerSize;
        if (orientation == FPLOrientation.HORIZONTAL) {
            scrollBar.setLocation(0, lastKnownContainerSize.height - scrollBarWidth);
            scrollBar.setSize(lastKnownContainerSize.width, scrollBarWidth);
            containerSize = lastKnownContainerSize.width;
        } else {
            scrollBar.setLocation(lastKnownContainerSize.width - scrollBarWidth, 0);
            scrollBar.setSize(scrollBarWidth, lastKnownContainerSize.height);
            containerSize = lastKnownContainerSize.height;
        }


        contentSize = (long) panelCount * (long) panelSize;
        final long invisibleStuff = contentSize - containerSize;
        actualScrollPosition = Math.max(0, Math.min(invisibleStuff,
                                                    (long) (getScrollBarPosRatio() * (invisibleStuff))
        ));


        final int extent;
        if (contentSize > 0) {
            final double visibleRatio = containerSize / (double) contentSize;
            extent = (int) Math.max(0, Math.min(Integer.MAX_VALUE, Integer.MAX_VALUE * visibleRatio));
        } else {
            extent = Integer.MAX_VALUE;
        }
        final int unitIncrement = (int) Math.max(1, Math.min(extent,
                                                             extent * fractionOfExtentToScrollPerArrowClick));
        final int blockIncrement = (int) Math.max(1, Math.min(extent,
                                                              extent * fractionOfExtentToScrollPerTrackClick));
        scrollBar.getModel().setExtent(extent);
        scrollBar.setUnitIncrement(unitIncrement);
        scrollBar.setBlockIncrement(blockIncrement);
        scrollBar.setVisible(!hideScrollbarWhenUnnecessary || extent < Integer.MAX_VALUE);

        final Dimension panelSizes = getPanelSize();

        long n = actualScrollPosition;
        final long endOfScreen = actualScrollPosition + containerSize + panelSize;
        final Map<Integer, T> newKnownPanels = new HashMap<>();

        while (n < endOfScreen) { // Loop ongoing = need more panels to fill the view.

            // Calc index of current panel.
            final long panelIndex = n / panelSize;
            if (panelIndex > Integer.MAX_VALUE) {
                throw new Error();
            } else if (panelIndex >= panelCount) {
                break;
            }
            final int panelIndexInt = (int) panelIndex;


            // Obtain current panel - if possible from cache, else from external provider (which might likely create it from scratch).
            T panel = knownPanels.get(panelIndexInt);
            if (panel == null) {
                panel = panelSupplier.apply(panelIndexInt);
                if (panel == null) {
                    throw new IllegalArgumentException("panelSupplier returned null for index " + panelIndex);
                }
            }
            newKnownPanels.put(panelIndexInt, panel);


            // Set position and size.
            final int panelPos = (int) ((panelIndex * panelSize) - actualScrollPosition);
            final Point location;
            if (orientation == FPLOrientation.HORIZONTAL) {
                location = new Point(panelPos, 0);
            } else {
                location = new Point(0, panelPos);
            }
            panel.setLocation(location);
            panel.setSize(panelSizes);


            n += panelSize;
        }
        knownPanels = newKnownPanels; // Will now contain all panels needed for display. All panels that were in the map, but are no longer needed, are now gone forever.


        // Layout.
        container.add(scrollBar);
        for (JPanel panel : newKnownPanels.values()) {
            container.add(panel);
            panel.revalidate();
        }


        container.repaint(); // required
    }


    /**
     * @return the correct width&height a contained JPanel needs to have. Is applied by update() automatically.
     */
    public Dimension getPanelSize() {

        if (orientation == FPLOrientation.HORIZONTAL) {
            return new Dimension(panelSize,
                                 lastKnownContainerSize.height - (scrollBar.isVisible() ? scrollBarWidth : 0));
        } else {
            return new Dimension(lastKnownContainerSize.width - (scrollBar.isVisible() ? scrollBarWidth : 0),
                                 panelSize);
        }
    }


    /**
     * @return 0 to 1, expressing position of scroll bar handle.
     */
    public double getScrollBarPosRatio() {

        final int scrollRangeSize = Integer.MAX_VALUE - scrollBar.getVisibleAmount(); // Which should really be named getExtent(). Or rather the other way round.
        return scrollBar.getValue() / (double) scrollRangeSize;
    }


}

(EDIT: I've used this for a while now and am surprised. Fastest list component I've seen so far. Why haven't I seen this before?)

I just whipped up something that is not a JList, so it lacks a lot of features, but you can rather easily add those.

But what you get is this: A list (All members equal size.) that can easily hold ~2 billion panels with no memory or performance problems - see demo code. Also, the JPanels can contain anything you want, those components will work normally.

In the demo, the JPanel members have no inner JPanels and are completely transparent to mouse events (except for the JButtons, and that's good): A listener added to the overall container receives them, as shown in the demo. If you add more component hierarchy, things might get tricky, IDK.

Anyway, this thing is lightning fast and, most of all, gets the job done: JPanels in a list that you can operate but also select. (No selection code built-in, but like I said: Easy to do. Mouse hover demo code inside.)

enter image description here

Demo class:

final public class FastPanelListDemo {


    private static JFrame window = null;
    private static FastPanelList panelList = null;


    public static void main(final String[] args) {

        SwingUtilities.invokeLater(() -> {

            setLookAndFeelDefault();

            panelList = new FastPanelList(FastPanelList.FPLOrientation.VERTICAL,
                                          FastPanelListDemo::supplyPanel,
                                          0.1,
                                          0.95,
                                          false,
                                          80,
                                          Integer.MAX_VALUE);
            final Container contentPane = panelList.container;
            contentPane.setPreferredSize(new Dimension(300, 800));
            contentPane.setBackground(Color.GRAY);

            window = new JFrame("FastPanelList demo");
            window.setContentPane(contentPane);
            window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            window.pack();
            window.setLocationRelativeTo(null);
            window.setVisible(true);


            contentPane.addMouseMotionListener(new MouseAdapter() {

                @Override
                public void mouseMoved(final MouseEvent e) {

                    final JPanel itemUnderMouse = panelList.getItemUnderMouse(e);
                    if (itemUnderMouse != null) {
                        itemUnderMouse.setBackground(new Color((float) Math.random(),
                                                               (float) Math.random(),
                                                               (float) Math.random()));
                    }
                }
            });

        });
    }


    private static JPanel supplyPanel(final int panelIndex) { // Just supply something that extends JPanel. You can put as much data in as you want. E.g. "boolean isMouseHovering" etc.

        final JLabel label = new JLabel("panel " + panelIndex);
        label.setHorizontalAlignment(SwingConstants.CENTER);
        label.setVerticalAlignment(SwingConstants.CENTER);

        final JButton button = new JButton("click me");
        button.addActionListener(e -> {
            JOptionPane.showMessageDialog(window,
                                          "That was button " + panelIndex + ".",
                                          "* CLICK *",
                                          JOptionPane.INFORMATION_MESSAGE);
        });

        final JPanel panel = new JPanel(new BorderLayout(0,
                                                         0));
        panel.setBorder(BorderFactory.createEmptyBorder(10,
                                                        10,
                                                        10,
                                                        10));
        panel.setBackground(new Color((float) Math.random(),
                                      (float) Math.random(),
                                      (float) Math.random()));
        panel.add(label, BorderLayout.CENTER);
        panel.add(button, BorderLayout.EAST);

        return panel;
    }


    private static void setLookAndFeelDefault() {

        setLookAndFeel("Windows",
                       UIManager.getSystemLookAndFeelClassName(),
                       UIManager.getCrossPlatformLookAndFeelClassName(),
                       "Windows Classic",
                       "Nimbus",
                       "Metal",
                       "CDE/Motif");
    }


    /**
     * @param intendedLAFIs ANYTHING, but ideally a LookAndFeel name or several. The first value that equalsIgnoreCase
     *                      an installed LookAndFeelInfo.getName() will be used.
     */
    private static void setLookAndFeel(final String... intendedLAFIs) {

        if (intendedLAFIs != null && intendedLAFIs.length > 0) {
            final UIManager.LookAndFeelInfo[] installedLAFIs = UIManager.getInstalledLookAndFeels();
            LAFILOOP:
            for (String intendedLAFI : intendedLAFIs) {
                for (final UIManager.LookAndFeelInfo lafi : UIManager.getInstalledLookAndFeels()) {
                    if (lafi.getName().equalsIgnoreCase(intendedLAFI)) {
                        try {
                            UIManager.setLookAndFeel(lafi.getClassName());
                            break LAFILOOP;
                        } catch (Exception e) {
                            continue LAFILOOP;
                        }
                    }
                }
            }
        } else {
            throw new IllegalArgumentException("intendedLAFIs is null or empty.");
        }
    }
}

FastPanelList class:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;




/**
 * FastPanelList v[2pre1, 2019-05-28 10!00 UTC] by Dreamspace President
 */
final public class FastPanelList<T extends JPanel> {


    public enum FPLOrientation {
        HORIZONTAL(Adjustable.HORIZONTAL),
        VERTICAL(Adjustable.VERTICAL);


        final public int orientationAsConstant;


        FPLOrientation(final int orientationAsConstant) {


            this.orientationAsConstant = orientationAsConstant;
        }


    }




    final public FPLOrientation orientation;
    final private Function<Integer, T> panelSupplier;
    final private double fractionOfExtentToScrollPerArrowClick;
    final private double fractionOfExtentToScrollPerTrackClick;
    final private double fractionOfExtentToScrollPerMouseWheelStep;
    final private boolean hideScrollbarWhenUnnecessary;

    final private JScrollBar scrollBar;
    final private int scrollBarWidth; // The default width it normally has in any GUI.
    final public JPanel container; // The container of it all.

    private int panelSize = 0;  // The horizontal or vertical extent of each contained panel.
    private int panelCount = 0; // The amount of panels, indeed max Integer.MAX_VALUE.

    private long contentSize = 0; // The sum total extent of all "contained panels". (They're not really contained, but nobody will see that.)
    private long actualScrollPosition = 0; // The true scroll position, think contentSize.
    private Dimension lastKnownContainerSize = new Dimension(0, 0);


    private Map<Integer, T> knownPanels = new HashMap<>(); // All panels of which some pixels are currently potentially visible are cached here.


    /**
     * @param orientation                           Whether horizontal or the more common vertical arrangement.
     * @param panelSupplier                         Your code that supplies the panels as needed on the fly. The
     *                                              argument will NEVER be null - and your return value, too, must never
     *                                              be null.
     * @param fractionOfExtentToScrollPerArrowClick E.g. 0.1 for 10% of the visible area to become hidden/shown when you
     *                                              click a scrollbar arrow.
     * @param fractionOfExtentToScrollPerTrackClick E.g. 0.95 for 95% of the visible area to become hidden/shown when
     *                                              you click in the scrollbar track.
     * @param hideScrollbarWhenUnnecessary          Guess.
     * @param panelSize                             Can later also be done via setter. (Not tested.) KEEP IN MIND THAT
     *                                              THIS IS NOT YET SCALED, so if you have Desktop scaling 200% and are
     *                                              running Java 8, you need to double the value (e.g. use my GUIScaling
     *                                              class to automate this).
     * @param panelCount                            dto.
     */
    public FastPanelList(final FPLOrientation orientation,
                         final Function<Integer, T> panelSupplier,
                         final double fractionOfExtentToScrollPerArrowClick,
                         final double fractionOfExtentToScrollPerTrackClick,
                         final double fractionOfExtentToScrollPerMouseWheelStep,
                         final boolean hideScrollbarWhenUnnecessary,
                         final int panelSize,
                         final int panelCount) {


        if (orientation == null) {
            throw new IllegalArgumentException("orientation is null.");
        }
        if (panelSupplier == null) {
            throw new IllegalArgumentException("panelSupplier is null.");
        }

        this.orientation = orientation;
        this.panelSupplier = panelSupplier;
        this.fractionOfExtentToScrollPerArrowClick = Math.max(0, fractionOfExtentToScrollPerArrowClick);
        this.fractionOfExtentToScrollPerTrackClick = Math.max(0, fractionOfExtentToScrollPerTrackClick);
        this.fractionOfExtentToScrollPerMouseWheelStep = Math.max(0, fractionOfExtentToScrollPerMouseWheelStep);
        this.hideScrollbarWhenUnnecessary = hideScrollbarWhenUnnecessary;
        setPanelSize(panelSize);
        setPanelCount(panelCount);

        scrollBarWidth = determineScrollBarDefaultWidth();
        scrollBar = new JScrollBar(orientation.orientationAsConstant, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
        scrollBar.addAdjustmentListener(e -> update());

        container = new JPanel(null); // NULL: We want to layout everything manually.
        //        container.add(scrollBar);
        container.addComponentListener(new ComponentAdapter() {

            @Override
            public void componentResized(final ComponentEvent e) {

                update();
            }
        });

        container.addMouseWheelListener(this::mouseWheelEvent);

    }


    public void mouseWheelEvent(final MouseWheelEvent e) {

        final int rotation = e.getWheelRotation();
        final int extent = scrollBar.getModel().getExtent();
        final int increment = (int) Math.max(1, Math.min(extent,
                                                         extent * fractionOfExtentToScrollPerMouseWheelStep));

        scrollBar.setValue(scrollBar.getValue() + (rotation * increment));
    }


    private int determineScrollBarDefaultWidth() { // Called only ONE time.

        final JScrollPane dummyForDefaultSize = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
                                                                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        dummyForDefaultSize.setPreferredSize(new Dimension(1000, 1000));
        dummyForDefaultSize.setSize(dummyForDefaultSize.getPreferredSize());
        dummyForDefaultSize.doLayout();
        return dummyForDefaultSize.getVerticalScrollBar().getSize().width;
    }


    /**
     * FastPanelList requires each item to have the exact same size. This is where you define it (if you reconsidered
     * after your constructor call).
     *
     * @param panelSize Will become >=1
     */
    public void setPanelSize(final int panelSize) {

        this.panelSize = Math.max(1, panelSize);
    }


    /**
     * FastPanelList easily manages Integer.MAX_VALUE (about 2 billion) panels with no memory or performance problems.
     * You define the amount here. You don't add/remove panels in this thing: Instead, you will be asked to provide
     * panels as required depending on screen layout etc.
     *
     * @param panelCount Will become >=0
     */
    public void setPanelCount(final int panelCount) {

        this.panelCount = Math.max(0, panelCount);
    }


    /**
     * Clears the internal JPanel cache. Necessary if you want to repopulate the list. Setting the panel count and
     * calling update() is not sufficient. (Call update AFTER this method.)
     */
    public void clear() {

        knownPanels.clear();
    }


    public T getItemUnderMouse(final MouseEvent e) {

        return getItemUnderMouse(e.getX(), e.getY());
    }


    public T getItemUnderMouse(final int xInComponent,
                               final int yInComponent) {

        final long realPositionUnderMouse = (actualScrollPosition + (orientation == FPLOrientation.HORIZONTAL ? (long) xInComponent : (long) yInComponent));

        final int indexUnderMouse = (int) (realPositionUnderMouse / panelSize);
        return knownPanels.get(indexUnderMouse);
    }


    /**
     * This method is very lenient.
     *
     * @param index                   Anything.
     * @param callSupplierIfNotCached Depends on what you're trying to achieve. E.g. use FALSE if you want to set the
     *                                background color of one of the visible JPanels. The method would return null if
     *                                the panel is not visible, because then it is also no longer cached.
     * @return NULL if index is NULL, or is less than 0, or is equal to or greater than panelCount. Else the cached
     * JPanel (or whatever it secretly is via "extends"), meaning one of the panels that are currently visible or at the
     * edge of visibility. In all other cases, either null will be returned - or your supplier will be called, so you
     * get your own JPanel spat right back at you.
     */
    public T getItem(final Integer index,
                     final boolean callSupplierIfNotCached) {

        T ret = null;
        if (index != null && index >= 0 && index < panelCount) {
            ret = knownPanels.get(index);
            if (ret == null && callSupplierIfNotCached) {
                ret = panelSupplier.apply(index);
                if (ret == null) {
                    throw new IllegalArgumentException("panelSupplier returned null for index " + index);
                }
            }
        }
        return ret;
    }


    /**
     * @return a NEW Map containing the Map entries of the internal knownPanels map. These maps contain all panels that
     * are currently visible on screen. The index is identical to the number handed to your Supplier.
     * <p>
     * The purpose of that internal map is to not request EVERY panel anew every time, but only the panels that are
     * scrolled in at the edge of the screen. Obviously, this is also very useful to you, because you can call this
     * method to get all panels to change their look, data, whatever. And ONLY THOSE panels need to be changed. All
     * others ... don't exist. They only exist in the fantasy of the user. Until they scroll there, then some have
     * become real while others have fallen out of existence.
     */
    public Map<Integer, T> getCachedItems() {

        return new HashMap<>(knownPanels);
    }


    /**
     * This layouts the component. This is done automatically when the scrollbar is moved or the container is resized,
     * but any other action would require YOU to call this.
     */
    public void update() {

        container.removeAll();

        lastKnownContainerSize = container.getSize();

        final int containerSize;
        if (orientation == FPLOrientation.HORIZONTAL) {
            scrollBar.setLocation(0, lastKnownContainerSize.height - scrollBarWidth);
            scrollBar.setSize(lastKnownContainerSize.width, scrollBarWidth);
            containerSize = lastKnownContainerSize.width;
        } else {
            scrollBar.setLocation(lastKnownContainerSize.width - scrollBarWidth, 0);
            scrollBar.setSize(scrollBarWidth, lastKnownContainerSize.height);
            containerSize = lastKnownContainerSize.height;
        }


        contentSize = (long) panelCount * (long) panelSize;
        final long invisibleStuff = contentSize - containerSize;
        actualScrollPosition = Math.max(0, Math.min(invisibleStuff,
                                                    (long) (getScrollBarPosRatio() * (invisibleStuff))
        ));


        final int extent;
        if (contentSize > 0) {
            final double visibleRatio = containerSize / (double) contentSize;
            extent = (int) Math.max(0, Math.min(Integer.MAX_VALUE, Integer.MAX_VALUE * visibleRatio));
        } else {
            extent = Integer.MAX_VALUE;
        }
        final int unitIncrement = (int) Math.max(1, Math.min(extent,
                                                             extent * fractionOfExtentToScrollPerArrowClick));
        final int blockIncrement = (int) Math.max(1, Math.min(extent,
                                                              extent * fractionOfExtentToScrollPerTrackClick));
        scrollBar.getModel().setExtent(extent);
        scrollBar.setUnitIncrement(unitIncrement);
        scrollBar.setBlockIncrement(blockIncrement);
        scrollBar.setVisible(!hideScrollbarWhenUnnecessary || extent < Integer.MAX_VALUE);

        final Dimension panelSizes = getPanelSize();

        long n = actualScrollPosition;
        final long endOfScreen = actualScrollPosition + containerSize + panelSize;
        final Map<Integer, T> newKnownPanels = new HashMap<>();

        while (n < endOfScreen) { // Loop ongoing = need more panels to fill the view.

            // Calc index of current panel.
            final long panelIndex = n / panelSize;
            if (panelIndex > Integer.MAX_VALUE) {
                throw new Error();
            } else if (panelIndex >= panelCount) {
                break;
            }
            final int panelIndexInt = (int) panelIndex;


            // Obtain current panel - if possible from cache, else from external provider (which might likely create it from scratch).
            T panel = knownPanels.get(panelIndexInt);
            if (panel == null) {
                panel = panelSupplier.apply(panelIndexInt);
                if (panel == null) {
                    throw new IllegalArgumentException("panelSupplier returned null for index " + panelIndex);
                }
            }
            newKnownPanels.put(panelIndexInt, panel);


            // Set position and size.
            final int panelPos = (int) ((panelIndex * panelSize) - actualScrollPosition);
            final Point location;
            if (orientation == FPLOrientation.HORIZONTAL) {
                location = new Point(panelPos, 0);
            } else {
                location = new Point(0, panelPos);
            }
            panel.setLocation(location);
            panel.setSize(panelSizes);


            n += panelSize;
        }
        knownPanels = newKnownPanels; // Will now contain all panels needed for display. All panels that were in the map, but are no longer needed, are now gone forever.


        // Layout.
        container.add(scrollBar);
        for (JPanel panel : newKnownPanels.values()) {
            container.add(panel);
            panel.revalidate();
        }


        container.repaint(); // required
    }


    /**
     * @return the correct width&height a contained JPanel needs to have. Is applied by update() automatically.
     */
    public Dimension getPanelSize() {

        if (orientation == FPLOrientation.HORIZONTAL) {
            return new Dimension(panelSize,
                                 lastKnownContainerSize.height - (scrollBar.isVisible() ? scrollBarWidth : 0));
        } else {
            return new Dimension(lastKnownContainerSize.width - (scrollBar.isVisible() ? scrollBarWidth : 0),
                                 panelSize);
        }
    }


    /**
     * @return 0 to 1, expressing position of scroll bar handle.
     */
    public double getScrollBarPosRatio() {

        final int scrollRangeSize = Integer.MAX_VALUE - scrollBar.getVisibleAmount(); // Which should really be named getExtent(). Or rather the other way round.
        return scrollBar.getValue() / (double) scrollRangeSize;
    }


}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文