I'm having an issue in my application. I wish to dynamically create several "tabbed" scrollpanes that hold a semi-large amount of objects called "ImageLabels", which are just labels with (you guessed it) images on them.
I made most of my gui in Netbeans, as it is semi-complicated and I'm too nooby to do it myself.
My problem is this: When adding my scrollpane objects (that contain a JPanel with a GridLayout), and after I attempt to add my imagelabels to the JPanel with the layout (my constructor is set to "new GridLayout(0, 5, 5, 5);" which is unlimited rows, 5 columns, with 5 pxls of space.) nothing shows up, nor does the scrollpanel go into "scroll" mode.
I am completely at a loss and have been trying all day with different layouts. Here's some code I have...
Constructor of the ImageLabel object..
addMouseListener(this);
setVisible(true);
setPreferredSize(new Dimension(32, 32));
How I set up my tileHolderPanel JPanel (the panel holding the ImageLabels)
myLayout = new GridLayout(0, 5, 5, 5);
tileHolderPanel.setLayout(myLayout);
and after constructing all of the ImageLabels I wish to add to each panel, I simply do
tileHolderPanel.add(label);
If it matters, my TileHolderPanel is inside of a scrollpane which is inside a JPanel,and they have the default GroupLayouts which Netbeans generates.
Thank you,
-Luke
Here's a working example with which to compare your code.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
/** #see http://stackoverflow.com/questions/7801870 */
public class ScrollGroup extends JPanel {
private static final int N = 8;
private static final int NN = N * N;
private static final int GAP = 5;
private static final int SIZE = 32;
public ScrollGroup() {
this.setLayout(new GridLayout(N, N, GAP, GAP));
for (int i = 0; i < NN; i++) {
JLabel label = new JLabel();
label.setOpaque(true);
label.setBackground(Color.getHSBColor((float) i / NN, 1, 1));
label.setPreferredSize(new Dimension(SIZE, SIZE));
this.add(label);
}
}
private void display() {
JFrame f = new JFrame("ScrollGroup");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane sp = new JScrollPane(this);
GroupLayout layout = new GroupLayout(f.getContentPane());
f.setLayout(layout);
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
layout.setHorizontalGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup().addComponent(sp)));
layout.setVerticalGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup().addComponent(sp)));
f.pack();
f.setSize(N * SIZE, N * SIZE);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ScrollGroup().display();
}
});
}
}
I wish to dynamically create ...
When you add components to a visible GUI the basic code should be:
panel.add(...);
panel.revalidate();
If you need more help then you need to post an SSCCE.
Related
I'm trying to create something super basic but after getting super frustrated I figured it was time to ask here.
Desired result:
Right now here is my code:
GUI class
package bookingProject;
import java.awt.Color;
import javax.swing.JPanel;
import java.awt.GridLayout;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
public class GUI extends javax.swing.JFrame {
JFrame frame = new JFrame();
JPanel silverPanel = new JPanel();
JPanel goldPanel = new JPanel();
Button buttons[] = new Button[30];
public static void main(String args[]) {
new GUI();
}
public GUI() {
setSize(500, 500);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
JPanel bronzePanel = new JPanel();
bronzePanel.setLayout(new GridLayout(3, 10));
bronzePanel.setBackground(Color.red);
for (int i = 0; i < 30; i++) {
buttons[i] = new Button();
bronzePanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
bronzePanel.add(buttons[i]);
}
add(bronzePanel);
setVisible(true);
silverPanel.setLayout(new GridLayout(3, 10));
silverPanel.setBackground(Color.yellow);
for (int i = 0; i < 30; i++) {
buttons[i] = new Button();
silverPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
silverPanel.add(buttons[i]);
}
add(silverPanel);
setVisible(true);
goldPanel.setLayout(new GridLayout(3, 10));
goldPanel.setBackground(Color.green);
for (int i = 0; i < 30; i++) {
buttons[i] = new Button();
goldPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
goldPanel.add(buttons[i]);
}
add(goldPanel);
setVisible(true);
}
}
And a class for the Buttons I want to use
Button class
package bookingProject;
import java.awt.Color;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class Button extends JButton implements ActionListener{
public Button (){
this.addActionListener(this);
}
public void actionPerformed(ActionEvent e){
Object source = e.getSource();
if (source instanceof GUI){
((GUI)source).setBackground(Color.YELLOW);
}
}
}
This is what I'm getting:
I'm brand new to this stuff so forgive my complete ignorance/ability... but what I would like is 3 panels going top to bottom (bronze/silver/gold) each with a GridLayout of buttons... But all I can get is the bronze panel sticking infront of everything else. I think I'm supposed to use BoxLayout to sort the 3 panels into an order but I played around with this for about 4 hours and felt like I was getting nowhere fast.
I also need a way of making the buttons turn yellow when I press them but currently that isn't working; although I barely looked at that.
This is what I'm getting:
By default the content pane of a JFrame uses a BorderLayout. When you add components to the frame the components get added to the BorderLayout.CENTER by default because you didn't specify a constraint. Only one component can be displayed in the CENTER so only the last one added is visible.
I think I'm supposed to use BoxLayout
That is one approach (but probably not the easiest) as you would manually need to specify the space between each row of components.
The section from the Swing tutorial on How to Use Box Layout has a working example to get you started.
Easiest is to use a GridLayout with 3 rows and 2 columns as the layout manager for the frame. Then each individual panel can also use a GridLayout with 3 rows and 5 columns.
If you want spaces between the components in each of your panels then you need to look at the GridLayout API. It allows you to specify a vertical and horizontal gap between components.
Another approach would be to use a GridBagLayout, although this is a little more complicated because you need to specify constraints for each component added.
The tutorial also has sections on How to Use GridBag Layout and How to Use GridLayout.
Note the examples from the tutorial will also show you how to better structure your code so that component are created on the Event Dispatch Thread(EDT).
You can try GridBagLayout instead:
import javax.swing.JFrame;
import javax.swing.JButton;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import javax.swing.SwingUtilities;
import javax.swing.JPanel;
import java.awt.Insets;
import java.awt.Color;
class Demo{
public static void main(String[]args){
SwingUtilities.invokeLater(()->{
JFrame frame=new JFrame("Grid");
JPanel panel=(JPanel)frame.getContentPane();
GridBagConstraints gbc=new GridBagConstraints();
gbc.insets=new Insets(9,7,5,5);
panel.setLayout(new GridBagLayout());
panel.add(newGrid(Color.YELLOW,gbc, 0, 0),gbc);
panel.add(newGrid(Color.YELLOW,gbc, 1, 0),gbc);
panel.add(newGrid(Color.LIGHT_GRAY,gbc, 0, 1),gbc);
panel.add(newGrid(Color.LIGHT_GRAY,gbc, 1, 1),gbc);
panel.add(newGrid(Color.GREEN,gbc, 0, 2),gbc);
panel.add(newGrid(Color.GREEN,gbc, 1, 2),gbc);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
});
}
private static JPanel newGrid(Color color, GridBagConstraints pGbc, int pX, int pY){
JPanel panel=new JPanel(new GridBagLayout());
GridBagConstraints gbc=new GridBagConstraints();
gbc.insets=new Insets(5,5,5,5);
for(int x=0;5>x;x++){
for(int y=0;3>y;y++){
gbc.gridx=x;
gbc.gridy=y;
JButton btn=new JButton("<html> </html>");
btn.setBackground(color);
panel.add(btn,gbc);
}
}
pGbc.gridx=pX;
pGbc.gridy=pY;
return panel;
}
}
There's a simple application that uses JScrollPane for 2 lists and have 1 button to switch them. I want to add many more Swing elements, but I cannot move them with object.setBounds. Whatever I will write in this method element doesn't change its place and size.
package paka;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;;
public class Question extends JFrame {
private JList leftlist,rightlist;
private JButton movebutton;
private JLabel pointlessLabel;
private static String[] foods={"bacon","wings","ham","beef","more bacon"};
public Question(){
super("title");
setLayout(new FlowLayout());
leftlist=new JList(foods);
leftlist.setVisibleRowCount(3);
leftlist.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
add(new JScrollPane(leftlist));
movebutton = new JButton("Move");
movebutton.addActionListener(
new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
rightlist.setListData(leftlist.getSelectedValues());
}
}
);
add(movebutton);
rightlist = new JList();
rightlist.setVisibleRowCount(3);
rightlist.setFixedCellWidth(100);rightlist.setFixedCellHeight(15);
rightlist.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
add(new JScrollPane(rightlist));
//Okay, deleting everything below we have only two list with button that moves elements from 1st list to 2nd
movebutton = new JButton("Click me!");
movebutton.setBounds(700, 100, 80, 20);
add(movebutton);
pointlessLabel = new JLabel("I'm unbreakable");
pointlessLabel.setBounds(500,200,100,50);
add(pointlessLabel);
}
public static void main(String args[]){
Question go = new Question();
go.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
go.setSize(300,200);
go.setVisible(true);
}
}
You need to use combinations of Layout Managers (as stated by #AndrewThompson on his comment above) to achieve the same output that you have with a null layout.
Avoid the use of null layout, see: Null layout is evil and Why is it frowned upon to use a null layout in Swing?
With the code below you can have the same output you had with setBounds() method. I didn't added an ActionListener to this code, since I'm just demonstrating how to stop using null layout and have the same output. Yes! The second one seems shorter, that's because of pack() but you can still setSize() if you want / need an specific window size.
To add more elements below just add more JPanels and add them to pane, I hope this helps to solve your issue, and if not, please post a Minimal Complete and Verifiable Example that we can copy-paste, make it short, but still shows your issue and follows above recommendations
Important Note:
I'll bring a quote from This answer's comment of #MadProgrammer, because I used prototypeCellValue to make the rightList's width shorter:
I'd also be careful with prototypeCellValue, unless the value matches the expected length of your data, it could truncate your data when it's displayed, just need to be careful
import java.awt.FlowLayout;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class QuestionTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
String[] foods = { "bacon", "wings", "ham", "beef", "more bacon" };
JFrame frame;
JPanel topPane, bottomPane, pane;
JList leftList, rightList;
JButton moveButton, clicMeButton;
JScrollPane scroll;
JLabel label;
frame = new JFrame("title");
topPane = new JPanel();
bottomPane = new JPanel();
pane = new JPanel();
leftList = new JList(foods);
rightList = new JList();
moveButton = new JButton("Move");
clicMeButton = new JButton("Click me!");
label = new JLabel("I'm unbreakable");
leftList.setVisibleRowCount(3);
rightList.setVisibleRowCount(3);
leftList.setPrototypeCellValue(String.format("%30s", ""));
rightList.setPrototypeCellValue(String.format("%30s", ""));
pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
topPane.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
scroll = new JScrollPane(leftList);
topPane.add(scroll);
topPane.add(moveButton);
scroll = new JScrollPane(rightList);
topPane.add(scroll);
bottomPane.setLayout(new FlowLayout());
bottomPane.add(clicMeButton);
bottomPane.add(label);
pane.add(topPane);
pane.add(bottomPane);
frame.add(pane);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
I have a problem with showing specific component placed in JScrollPane. I have horizontal JScrollPane with GridLayout(1,0) and it contains variable number of JPanels - each containing image. It's like a preview of frames in GIF image. I use button to move among these JPanels (by changing borders and keeping index of chosen one), but I don't know how to force JScrollPane to show me JPanel if it's chosen (and center it if possible).
So I want this
force to do this:
Thanks in advance!
EDIT: almost working code with scrollRectToVisible() method
public class MiniatursPanel extends JPanel{
private int indexOfChosenFrame = 0;
private ArrayList<JPanel> frames;
private JScrollPane scrollPane;
private JPanel innerPanel;
public MiniatursPanel(){
setBorder(BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(),BorderFactory.createLoweredBevelBorder()));
setPreferredSize(new Dimension(1200,170));
setLayout(null);
}
public void initialize(){
int width = GifImageStats.getInstance().getWidth();
int height = GifImageStats.getInstance().getHeight();
int numberOfFrames = GifImageStats.getInstance().getNumberOfFrames();
frames = new ArrayList(numberOfFrames);
for (int i = 0; i < numberOfFrames; i++) {
JPanel frameBox = new JPanel();
frameBox.setLayout(new FlowLayout(FlowLayout.CENTER));
JButton button = new JButton(String.valueOf(i+1));
button.setPreferredSize(new Dimension(2*width,2*height));
button.setBackground(Color.white);
button.setFocusable(false);
frameBox.add(button);
frames.add(frameBox);
}
innerPanel = new JPanel();
innerPanel.setLayout(new GridLayout(1,0,10,10));
for (JPanel button : frames) {
innerPanel.add(button);
}
scrollPane = new JScrollPane(innerPanel);
scrollPane.setBounds(10, 10, 1180, 145);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
highlightFrame(frames.get(0));
add(scrollPane);
}
public void nextFrame(){
if (indexOfChosenFrame == frames.size() - 1) {
unhighlightFrame(frames.get(indexOfChosenFrame));
indexOfChosenFrame = 0;
highlightFrame(frames.get(0));
}else{
unhighlightFrame(frames.get(indexOfChosenFrame));
indexOfChosenFrame++;
highlightFrame(frames.get(indexOfChosenFrame));
}
}
public void previousFrame(){
if (indexOfChosenFrame == 0) {
unhighlightFrame(frames.get(0));
indexOfChosenFrame = frames.size()-1;
highlightFrame(frames.get(indexOfChosenFrame));
}else{
unhighlightFrame(frames.get(indexOfChosenFrame));
indexOfChosenFrame--;
highlightFrame(frames.get(indexOfChosenFrame));
}
}
private void highlightFrame(JPanel frame){
Rectangle rect = frame.getBounds();
rect.setBounds(frame.getX()-550, frame.getY(), frame.getWidth()+1050, frame.getHeight());
innerPanel.scrollRectToVisible(rect);
frame.setBorder(BorderFactory.createLineBorder(Color.red,2));
}
private void unhighlightFrame(JPanel frame){
frame.setBorder(null);
}
The relevant method here is JComponent#scrollRectToVisible(Rectangle). It has to be called on the component that is in the viewport of the scroll pane. (In your case, this is the panel with the grid layout, which contains the other sub-panels).
The rectangle that is passed to this method can be the bounds of one sub-panel. In this case, the scoll pane will do the "minimum" scrolling that is necessary to make the given rectangle visible. If you want to make sure that the respective sub-panel is in the center, then you can increase the size of this rectangle - that is, you define a rectangle in a way that the desired sub-panel will be in the center.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class ScrollToVisible
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
int n = 20;
final JPanel panel = new JPanel(new GridLayout(1,0));
final List<JComponent> components = new ArrayList<JComponent>();
for (int i=0; i<n; i++)
{
JComponent component = new JLabel(String.valueOf(i), SwingConstants.CENTER);
component.setPreferredSize(new Dimension(100,100));
component.setBorder(BorderFactory.createLineBorder(Color.BLACK));
components.add(component);
panel.add(component);
}
final JScrollPane scrollPane = new JScrollPane(panel);
final JSpinner spinner = new JSpinner(new SpinnerNumberModel(0, 0, n-1, 1));
spinner.addChangeListener(new ChangeListener()
{
JComponent selectedComponent = components.get(0);
#Override
public void stateChanged(ChangeEvent e)
{
selectedComponent.setBorder(BorderFactory.createLineBorder(Color.BLACK));
int index = (Integer)spinner.getValue();
JComponent component = components.get(index);
Rectangle bounds = component.getBounds();
// This would make the component "just" visible:
//panel.scrollRectToVisible(bounds);
// This will center the component:
int cx = bounds.x + bounds.width / 2;
int w = scrollPane.getViewport().getWidth();
Rectangle r = new Rectangle(cx-w/2, bounds.y, w, bounds.height);
panel.scrollRectToVisible(r);
selectedComponent = component;
selectedComponent.setBorder(BorderFactory.createLineBorder(Color.RED));
}
});
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(scrollPane, BorderLayout.CENTER);
f.getContentPane().add(spinner, BorderLayout.NORTH);
f.setSize(800, 300);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
EDIT: You should NOT use setLayout(null), and you should not do manual calls to setBounds, and you should rarely use setPreferredSize. And... when you post code that already is so close to a https://stackoverflow.com/help/mcve (or even was created from a runnable example of another post) then you should make it really runnable. It's annoying to re-insert the boilerplate code and waste some time with debugging until you realize that initialize() is not called at all...
However, change the code according to this:
private void highlightFrame(JPanel frame){
Rectangle rect = frame.getBounds();
int c = rect.x + rect.width / 2;
int w = scrollPane.getViewport().getWidth();
int x = c-w/2;
rect.setBounds(x, rect.y, w, rect.height);
innerPanel.scrollRectToVisible(rect);
frame.setBorder(BorderFactory.createLineBorder(Color.red,2));
}
private void unhighlightFrame(JPanel frame){
frame.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
}
The most important thing is to make sure that the size of the components is correct, by setting an empty border with the same size as the "highlighting" border.
I have a small problem when using JScrollPane in my Java application.
I have a JScrollPane containing a JPanel.
This JPanel is dynamically updated with buttons (vertically ordered) that can be of any width.
The JPanel automatically adjusts its width to the largest JButton component inside.
Now when the vertical scrollbar appears, it takes away some space on the right side of my JPanel, which causes the largest buttons not to appear completely. I don't want to use a horizontal scrollbar in addition to display the whole button.
Is there a way to resize my JPanel when a scrollbar appears, so it appears nicely next to my buttons? Or is there any other way to have the scrollbar appear next to my JPanel?
Thanks in advance!
EDIT: Here is a demo of my problem. When you resize the window to a smaller height, a little part of the buttons on the right side gets covered.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import java.awt.GridLayout;
/**
* #author Dylan Kiss
*/
public class Demo {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame myFrame = new JFrame("Demo");
JPanel sideBar = new JPanel();
JPanel centerPanel = new JPanel();
centerPanel.add(new JLabel("This is the center panel."));
JPanel buttonContainer = new JPanel();
JButton myButton = null;
for (int i = 0; i < 20; i++) {
buttonContainer.setLayout(new GridLayout(20, 1, 0, 0));
myButton = new JButton("This is my button nr. " + i);
buttonContainer.add(myButton);
}
sideBar.setLayout(new BorderLayout(0, 0));
JScrollPane scrollPane = new JScrollPane(buttonContainer);
sideBar.add(scrollPane);
myFrame.getContentPane().add(sideBar, BorderLayout.WEST);
myFrame.getContentPane().add(centerPanel, BorderLayout.CENTER);
myFrame.setLocationByPlatform(true);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.pack();
myFrame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
Here is a simple, not pretty, solution:
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
EDIT:
I thought that might not do the job in your case. Here is a better solution although it has quite a lot of boilerplate:
private class ButtonContainerHost extends JPanel implements Scrollable {
private static final long serialVersionUID = 1L;
private final JPanel buttonContainer;
public ButtonContainerHost(JPanel buttonContainer) {
super(new BorderLayout());
this.buttonContainer = buttonContainer;
add(buttonContainer);
}
#Override
public Dimension getPreferredScrollableViewportSize() {
Dimension preferredSize = buttonContainer.getPreferredSize();
if (getParent() instanceof JViewport) {
preferredSize.width += ((JScrollPane) getParent().getParent()).getVerticalScrollBar()
.getPreferredSize().width;
}
return preferredSize;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return orientation == SwingConstants.HORIZONTAL ? Math.max(visibleRect.width * 9 / 10, 1)
: Math.max(visibleRect.height * 9 / 10, 1);
}
#Override
public boolean getScrollableTracksViewportHeight() {
if (getParent() instanceof JViewport) {
JViewport viewport = (JViewport) getParent();
return getPreferredSize().height < viewport.getHeight();
}
return false;
}
#Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return orientation == SwingConstants.HORIZONTAL ? Math.max(visibleRect.width / 10, 1)
: Math.max(visibleRect.height / 10, 1);
}
}
It implements Scrollable to get full control of scrolling, does a fancy trick with tracking the viewport height to ensure the buttons expand when the space is available and adds on the width of the vertical scroll bar to the preferred width at all times. It could expand when the vertical scroll bar is visible but that looks bad anyway. Use it like this:
scrollPane = new JScrollPane(new ButtonContainerHost(buttonContainer));
It looks to me like this workaround is required because of a possible bug in javax.swing.ScrollPaneLayout:
if (canScroll && (viewSize.height > extentSize.height)) {
prefWidth += vsb.getPreferredSize().width;
}
Here extentSize is set to the preferred size of the viewport and viewSize is set to viewport.getViewSize(). This does not seem correct, AFAIK the size of the view inside the viewport should always equal the preferred size. It seems to me that the view size should be compared to the actual size of the viewport rather than its preferred size.
A simple workaround to meet your demands regarding
Is there a way to resize my JPanel when a scrollbar appears, so it
appears nicely next to my buttons?
is the use of EmptyBorder, this will let you achieve what you feel like, should happen, as shown in the image below :
I just added this line written below after this line JPanel buttonContainer = new JPanel();
ADDED LINE
buttonContainer.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
Here is your code with that added line :
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import java.awt.GridLayout;
/**
* #author Dylan Kiss
*/
public class Demo
{
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
try
{
JFrame myFrame = new JFrame("Demo");
JPanel sideBar = new JPanel();
JPanel centerPanel = new JPanel();
centerPanel.add(new JLabel("This is the center panel."));
JPanel buttonContainer = new JPanel();
buttonContainer.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
JButton myButton = null;
for (int i = 0; i < 20; i++)
{
buttonContainer.setLayout(new GridLayout(20, 1, 0, 0));
myButton = new JButton("This is my button nr. " + i);
buttonContainer.add(myButton);
}
sideBar.setLayout(new BorderLayout(0, 0));
JScrollPane scrollPane = new JScrollPane(buttonContainer);
sideBar.add(scrollPane);
myFrame.getContentPane().add(sideBar, BorderLayout.WEST);
myFrame.getContentPane().add(centerPanel, BorderLayout.CENTER);
myFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
myFrame.pack();
myFrame.setLocationByPlatform(true);
myFrame.setVisible(true);
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
}
}
You could resize the JPanel by calling setPreferredSize when the JPanel needs to be resized.
buttonContainer.setPreferredSize(Dimension d);
The below code represents the problem. Since I have heights of the north and south panels set the rest of it goes to the center panel using GridLayout. I think that since it cannot share the leftover pixels equally among its rows it just leaves them. Therefore in the below code we have ugly white line over south panel.
My question here is: How to make sure that when the GridLayout is not taking the whole space it is at least centered?
Normally I would use TableLayout and situation is sorted, but since I was writing an answer I wanted to use only standard managers. Knowing this would be very useful for me thanks in advance.
Example:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class AligningButonsTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JFrame f = new JFrame();
f.setSize(800, 600);
double CONSTANT_FACTOR = .1;
int noOfRows = 5;
JPanel centerP = new JPanel(new GridLayout(noOfRows,1));
for(int i = 0; i < noOfRows; i++)
{
BoxPanel bP = new BoxPanel();
centerP.add(bP);
}
JPanel contentPane = new JPanel(new BorderLayout());
f.setContentPane(contentPane);
contentPane.add(centerP, BorderLayout.CENTER);
JPanel southP = new JPanel();
southP.setBackground(Color.RED.darker());//southP.setOpaque(false);
southP.setPreferredSize(new Dimension(1, (int)(CONSTANT_FACTOR* f.getHeight())));
contentPane.add(southP, BorderLayout.SOUTH);
JPanel northP = new JPanel();
northP.setBackground(Color.RED.darker());//northP.setOpaque(false);
northP.setPreferredSize(new Dimension(1, (int)(CONSTANT_FACTOR* f.getHeight())));
contentPane.add(northP, BorderLayout.NORTH);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
});
}
}
class BoxPanel extends JPanel
{
public BoxPanel()
{
setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, Color.RED));
setBackground(Color.DARK_GRAY);
}
}
How to make sure that when the GridLayout is not taking the whole space it is at least centered?
JPanel wrapper = new JPanel( new GridBagLayout() );
wrapper.add( centerP );
contentPane.add(wrapper, BorderLayout.CENTER);
//contentPane.add(centerP, BorderLayout.CENTER);
BoxLayout does a pretty good job of distributing the space between components using Box.createVerticalGlue(). This example uses Box.createVerticalStrut(), top and bottom. The spacers are described in How to Use BoxLayout: Using Invisible Components as Filler.
Addendum: BoxTest2 is a variation that uses BoxLayout to create fixed-size edge panels and vertical glue to distribute the space more evenly. Box.Filler may also be used to control the "leftover" vertical space.
/** #see http://stackoverflow.com/questions/6072956 */
public class BoxTest2 {
private static final int WIDE = 480;
private static final int HIGH = WIDE / 8;
private static final int ROWS = 5;
private static final Box center = new Box(BoxLayout.Y_AXIS);
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
center.setOpaque(true);
center.setBackground(Color.lightGray);
center.add(Box.createVerticalGlue());
center.add(new EdgePanel());
for (int i = 0; i < ROWS; i++) {
center.add(new BoxPanel());
}
center.add(new EdgePanel());
center.add(Box.createVerticalGlue());
f.add(center, BorderLayout.CENTER);
f.pack();
f.setVisible(true);
}
});
}
private static class EdgePanel extends JPanel {
public EdgePanel() {
Dimension d = new Dimension(WIDE, 2 * HIGH / 3);
setPreferredSize(d);
setBackground(Color.red.darker());
}
}
private static class BoxPanel extends JPanel {
public BoxPanel() {
setPreferredSize(new Dimension(WIDE, HIGH));
setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, Color.red));
setBackground(Color.darkGray);
}
}
}
Could you try perhaps nesting this center panel in either a BorderLayout.North or maybe even a FlowLayout.Center.
By this I mean:
JPanel holder = new JPanel(new BorderLayout());
holder.add(centerP,BorderLayout.NORTH);
contentPane.add(holder, BorderLayout.CENTER);
I cannot exactly visualize your problem so it is hard to write a solution.