I'm trying to create a horizontal JList with seven items inside a JScrollPane and only three of those items need to be visible and the others accessible via a scrollbar. Here's an example of what I'm trying to do:
This is what I tried to do:
JFrame frame = new JFrame("JList Example");
String[] items = {"Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10"};
JList<String> list = new JList<>(items);
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
list.setVisibleRowCount(1);
JScrollPane scrollPane = new JScrollPane(list);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
frame.add(scrollPane);
And this is what I got:
So I can't find a way to limit the number of visible items other than one way to setPreferredSize of the JScrollPane. Which is not a very good way as described here, and it doesn't really work for me. Is there another way to do what I want?
P.S. I'm not good at English, so sorry if you misunderstood what I want. Trying my best
I just want to point out the obvious that "only three of those items need to be visible" is problematic, as the first three values might be larger or smaller then the following items or with different data you'll get different, and sometimes, weird results.
Do we make use of a "prototype" value, the model's first element or try and get the first n
items from the model to calculate the "desirable viewable width"? What happens when the model contains less then n
elements or no elements at all?
The point is, there's a "lot" to think about. If you just want to reduce the horizontal size of the component, you could just override the getPreferredScrollableViewportSize
and return a "desirable" width based on you needs, but just remember that this not likely to be n
columns.
The following example basically demonstrates this concept (as best as I can without trying to figure out each and every possible edge case).
It extends JList
and overrides the getPreferredScrollableViewportSize
method.
visibleColumnCount
is 0 or less, it will operate as normalfixedCellWidth
is defined, this is multiplied by visibleColumnCount
prototypeCellValue
is set, the rendered values width is multiplied by visibleColumnCount
256
(just pick a number)visibleColumnCount
, it sums the preferred width of each element, averages the amount and fills in the remaining columns with the average ... if that sounds confusing, you should have tried coming up with it 😳n
columns and calculate their preferred widthsFor example...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListModel;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
String[] items = {"Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10"};
FixedColumnWidthList<String> list = new FixedColumnWidthList<>(items);
list.setVisibleColumnCount(3);
JScrollPane scrollPane = new JScrollPane(list);
add(scrollPane);
}
}
public class FixedColumnWidthList<T> extends JList<T> {
private int visibleColumnCount = -1;
public FixedColumnWidthList() {
configureDefaults();
}
public FixedColumnWidthList(ListModel<T> dataModel) {
super(dataModel);
configureDefaults();
}
public FixedColumnWidthList(T[] listData) {
super(listData);
configureDefaults();
}
public FixedColumnWidthList(Vector<T> listData) {
super(listData);
configureDefaults();
}
protected void configureDefaults() {
setLayoutOrientation(JList.HORIZONTAL_WRAP);
setVisibleRowCount(1);
}
public int getVisibleColumnCount() {
return visibleColumnCount;
}
public void setVisibleColumnCount(int visibleColumnCount) {
if (this.visibleColumnCount == visibleColumnCount) {
return;
}
this.visibleColumnCount = visibleColumnCount;
revalidate();
repaint();
}
@Override
public Dimension getPreferredScrollableViewportSize() {
int visibleColumnCount = getVisibleColumnCount();
Dimension original = super.getPreferredScrollableViewportSize();
if (visibleColumnCount <= 0) {
return original;
}
ListModel<T> model = getModel();
int desirableWidth = getFixedCellWidth();
if (desirableWidth <= 0) {
T testValue = getPrototypeCellValue();
if (testValue != null) {
desirableWidth = getCellRenderer().getListCellRendererComponent(this, testValue, 0, false, false).getPreferredSize().width;
desirableWidth *= visibleColumnCount;
} else if (getModel().getSize() == 0) {
desirableWidth = 256; // Just pick a number :/
} else {
desirableWidth = 0;
// You could, instead, just take the first value and mutiply
// it's rendered width by the number of visible columns,
// but this could mean you're not actually getting the
// first three columns ... but this whole thing is a bag
// of ambiguity
for (int column = 0; column < visibleColumnCount && column < model.getSize(); column++) {
int width = getCellRenderer().getListCellRendererComponent(this, model.getElementAt(column), column, false, false).getPreferredSize().width;
System.out.println(column + " = " + width);
desirableWidth += width;
}
// I can't find the "horizontal insets", so I'm guessing...
desirableWidth += (8 * (visibleColumnCount - 1));
// If we have less data then visible columns, just
// average the available information and multiply it by
// the remaining number of columns as a guess
if (visibleColumnCount > model.getSize()) {
int averageWidth = desirableWidth / model.getSize();
desirableWidth += (visibleColumnCount - model.getSize());
}
}
} else {
desirableWidth *= visibleColumnCount;
}
return new Dimension(desirableWidth, original.height);
}
}
}
I should not that the JavaDocs talk about "horizontal insets" but I can't find anywhere that it's accessible/available, so I made a guess. I suspect that this is probably a UI delegate option, so if you really want to make use of this example, I'd encourage you to experiment with it.
Now, having said all this, just override the getPreferredScrollableViewportSize
method and return a width
which otherwise meets your desirable result, messing about with "visible columns" is ambiguous and problematic