Reputation: 31
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?
Upvotes: 3
Views: 16110
Reputation: 1118
(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.)
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.");
}
}
}
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;
}
}
Upvotes: 1
Reputation: 343
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);
}
Upvotes: 4
Reputation: 109
goto this tutorial: it has the same concept with your problem...
http://docs.oracle.com/javase/tutorial/uiswing/components/combobox.html#renderer
Upvotes: 1
Reputation: 9249
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/rowGridLayout
will put all the JPanels
in a single column/row and make them all the same sizeUpvotes: 7