I'm currently adding JPanels to a JScrollPane by pressing a button:
However, I can't get the panels to stack on top of each other when adding more than 1 panel to the scroll pane. The previously added panel in the scroll pane seems to be getting removed when I call getViewport().add().
Is there a way to get a JScrollPane to save it's children and make my GUI look like this when adding multiple JPanels with a button press?
MainWindow class:
JScrollPane trackerPanel = new JScrollPane();
trackerPanel.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
trackerPanel.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
trackerPanel.setPreferredSize(new Dimension(500, 400));
frame.getContentPane().add(BorderLayout.NORTH, trackerPanel);
frame.setVisible(true);
//method: called from EnterButtonListener to create and append TrackerTile to GUI
public void addTrackerTile() {
incrementTrackerPanelCounter();
TrackerTile trackerTile = new TrackerTile();
trackerPanel.getViewport().add(trackerTile);
}
TrackerTile class:
public class TrackerTile extends JPanel implements Scrollable {
public Dimension preferredSize = new Dimension(794, 100);
public TrackerTile() {
setBorder(BorderFactory.createLineBorder(Color.BLACK));
}
#Override
public Dimension getPreferredSize() {
return preferredSize;
}
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(794, 100);
}
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 128;
}
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 128;
}
public boolean getScrollableTracksViewportWidth() {
return false;
}
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
The basic logic to create the GUI should be:
JPanel somePanel = new JPanel( some layout );
JScrollPane scrollPane = new JScrollPane( somePanel );
frame.add(scrollPane. BorderLayout.CENTER);
Then in the ActionListener that adds new panels you just do:
somePanel.add( new TrackerTile() );
So its just like adding components to a panel. The only difference is that the panal has been added to a scroll pane.
There is no need to implement Scrollable. But if you really want to customize the Scrollable properties, then you need to implement Scrollable on the "somePanel", not the individual components you add to "somePanel".
Or another option is to use the Scrollable Panel which implements the Scrollable interface and has methods that allow you to customize the behaviour.
Related
Currently working on a project and I need to add a panel I've made to a scrollpane or a table dynamically. The scrollpane should start out empty and add the panels.
The GuiConstructor is where i make the window.
My problem is that if I don't comment out the setSize in the GuiConstructor, the window starts out very small.
Secondly, when i press the add button, it doesn't add the panels.
public GuiConstructor(){
super(APPLICATION_NAME);
setLayout(new BorderLayout());
LoopControlWindow loopwin = new LoopControlWindow(connect);
add(loopwin , BorderLayout.NORTH);
pack();
setLocationRelativeTo(null);
setResizable(false);
setVisible(true);
//this.setSize(500, 500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public class LoopControlWindow extends JPanel {
IConnector connect;
public LoopControlWindow(IConnector connect) {
super(new BorderLayout());
this.connect = connect;
initPane();
}
private void initPane() {
JPanel panel = new JPanel(new GridLayout(3,1));
FolderSearchComp fsc = new FolderSearchComp(connect);
JScrollPane scrollPane = new JScrollPane();
JButton button = new JButton("Add");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
panel.add(new FolderSearchComp(connect));
scrollPane.getViewport().setView(panel);
}
});
scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setViewportBorder(new LineBorder(Color.BLACK));
add(scrollPane, BorderLayout.CENTER);
add(button, BorderLayout.SOUTH);
}
This is typical of this style of GUI app. You need to tell the layout manager how big to make the Window initialy without using setSize(). The way to do this is to override getPreferredSize() to return a default size. In your case:
public LoopControlWindow extends JPanel {
private Dimension size;
public LoopControlWindow() {
Preferences prefs = Preferences.userNodeForPackge("your.java.package");
size = new Dimension(prefs.getInt("width", 800), prefs.getInt("height", 600));
}
public Dimension getPreferredSize() {
return size;
}
}
By doing it this way you can store the user preferences for the window dimensions but also provide sensible defaults to start.
You should also make sure that this JPanel is your main panel and is added to the JFrame at BorderLayout.CENTER to ensure that your window gets drawn properly. All other panels should be somewhere inside this one.
Once you have this set up calling pack() will work correctly.
For your first problem, you need to specify a size for the initial JFrame(). One way is to call setSize as you are doing. Another is to override getPreferredSize() to return the default size. And one other option is to find the size of the user's monitor and set the JFrame to be a percentage of that size. That way you can ensure your window always fits on your user's screen.
int height = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice().getDefaultConfiguration().
getBounds().height;
height = (int) (height * .85);
int width = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice().getDefaultConfiguration().
getBounds().width;
width = (int) (width * .85);
frame.setSize(width, height);
Second, you need to call revalidate() and repaint() anytime you add or remove from a layout in order to see the changes.
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
panel.add(new FolderSearchComp(connect));
scrollPane.getViewport().setView(panel);
revalidate();
repaint();
}
});
One note on border layout. The components in it will not resize with your JFrame. Whatever component that is placed in BorderLayout.CENTER will, however. That component will grow to fill all extra space as the JFrame grows. It will also be the component that shrinks when the JFrame windows gets smaller.
I'm trying to make a kitchen display system using a FlowLayout and I'm trying to figure out a way to add another panel on the 2nd row when the first row is already full. The width of the GUI will change according to user preference. When wider, it should show more of the components per row.
Approach - Variable width with WrapLayout
The GridLayout solution presumes the GUI requires 6 components per row.
For as many cols as needed to fill the width, & then show the components in as many rows as required, look to WrapLayout.
Approach - Variable width with JList
A JList can also be used here, since it seems all the components consist of a single (GUI) object.
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.border.*;
public class ListComponents {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
String[] listData = {
"Component 1", "Component 2", "Component 3",};
final DefaultListModel<String> model
= new DefaultListModel<String>();
for (String datum : listData) {
model.addElement(datum);
}
JList list = new JList(model);
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
list.setVisibleRowCount(-1);
list.setCellRenderer(new ObjectCellRenderer());
Action addAction = new AbstractAction("Add New") {
#Override
public void actionPerformed(ActionEvent e) {
model.addElement("New Component");
}
};
JButton addNew = new JButton(addAction);
JPanel ui = new JPanel(new BorderLayout(3, 3));
ui.setBorder(new EmptyBorder(4, 4, 4, 4));
ui.add(new JScrollPane(list), BorderLayout.CENTER);
ui.add(addNew, BorderLayout.PAGE_START);
JFrame f = new JFrame("Component List");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setContentPane(ui);
f.pack();
f.setLocationByPlatform(true);
f.setVisible(true);
}
};
SwingUtilities.invokeLater(r);
}
}
class ObjectCellRenderer extends DefaultListCellRenderer {
Border border = new EmptyBorder(20, 5, 20, 5);
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(
list, value, index, isSelected, cellHasFocus);
label.setBorder(border);
return label;
}
}
Approach - Fixed width with GridLayout
Using a GridLayout, when we know we always want a fixed number per row, regardless of width.
JPanel mainPanel = new JPanel(new GridLayout(0,6));
See new GridLayout(rows,cols):
Creates a grid layout with the specified number of rows and columns. All components in the layout are given equal size.
One, but not both, of rows and cols can be zero, which means that any number of objects can be placed in a row or in a column.
See this code for an example using a GridLayout(0,2) for the labels beneath Add Another Label
You can use a FlowLayout. From the tutorial How to Use FlowLayout:
The FlowLayout class puts components in a row, sized at their preferred size. If the horizontal space in the container is too small to put all the components in one row, the FlowLayout class uses multiple rows. If the container is wider than necessary for a row of components, the row is, by default, centered horizontally within the container.
EDIT If you want your layout to scroll vertically when there are too many rows, you can use a JScrollPane with horizontal scrolling disabled. You can do that with:
js.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
(where js is the JScrollPane that contains your FlowLayout panel).
EDIT 2 Well, the above isn't enough. You also need to set a viewport view on the JScrollPane that will track the JScrollPane width. Here's a class that does this (taken from here):
static class MyPanel extends JPanel implements Scrollable{
#Override
public Dimension getPreferredScrollableViewportSize() {
return this.getPreferredSize();
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation, int direction) {
return 50;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction) {
return 80;
}
/*
* (non-Javadoc)
* #see javax.swing.Scrollable#getScrollableTracksViewportWidth()
*/
#Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
#Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
You would use this by adding your current FlowLayout layout to an instance of MyPanel (instead of directly to the JScrollPane) and then calling
js.setViewportView(myPanelInstance);
Initially all the components are aligning only in the Horizontal direction in a single row.
Then I set the size of scroll enabled panel as below
main.setPreferredSize(scroll.getViewport().getSize());
main.setMaximumSize(new Dimension(scroll.getViewport().getWidth(),Integer.MAX_VALUE));
And it worked the components start aligning in multiple lines,and not going beyond the screen horizontally. But why the vertical scroll is not happening, In fact the components are getting overridden, the height is also getting fixed as of viewport size even the maxsize is defined.
Please help.........
What i need is only vertical scrolling..... The components should not go beyond screen horizontally, but can go beyond screen vertically.
UPDATE with the code: Now all I want is the buttons should not go beyond the screen horizontally instead can use vertical scroll if the whole window is occupied.
public class Test {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setTitle("JAVA GUI");
JPanel main = new JPanel();
JScrollPane scroll = new JScrollPane(main);
BoxLayout box = new BoxLayout(main, BoxLayout.Y_AXIS);
main.setLayout(box);
main.add(new JLabel("row 1"));
JPanel panel1 = new JPanel(new FlowLayout());
for(int i=0;i<200;i++){
panel1.add(new JButton("b"+i));
}
JPanel panel2 = new JPanel(new FlowLayout());
for(int i=0;i<200;i++){
panel2.add(new JButton("b"+i));
}
JPanel panel3 = new JPanel(new FlowLayout());
for(int i=0;i<200;i++){
panel3.add(new JButton("b"+i));
}
Border border = BorderFactory.createLineBorder(Color.black,10);
panel1.setBorder(border);
panel2.setBorder(border);
panel3.setBorder(border);
main.add(panel1);
main.add(new JLabel("row2"));
main.add(panel2);
main.add(new JLabel("row3"));
main.add(panel3);
frame.setContentPane(scroll);
frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
frame.setVisible(true);
}
}
Scrollbars only appear when the preferred size of the component is greater than the size of the scrollpane.
A FlowLayout will wrap components, but it will not change the preferred size.
Check out the Wrap Layout which extends FlowLayout and will recalculate the preferred size for you when components wrap.
As camickr pointed out in https://stackoverflow.com/a/21816061 , the main problem here is that the FlowLayout does not properly compute the preferred size of the component. The WrapLayout that he linked solves this issue basically. But in this case, this is still not sufficient, because the preferred size of the panels will simply be "as wide as necessary to show all buttons". In order to really wrap the buttons, you also have to replace your main panel with a panel that implements the Scrollable interface and returns true in Scrollable#getScrollableTracksViewportWidth()
class ScrollablePanel extends JPanel implements Scrollable
{
#Override
public Dimension getPreferredScrollableViewportSize()
{
return getPreferredSize();
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation, int direction)
{
return 1;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction)
{
return 1;
}
#Override
public boolean getScrollableTracksViewportWidth()
{
return true;
}
#Override
public boolean getScrollableTracksViewportHeight()
{
return false;
}
}
So in your code:
JPanel main = new ScrollablePanel(); // Use ScrollablePanel here
JScrollPane scroll = new JScrollPane(main);
...
JPanel panel1 = new JPanel(new WrapLayout()); // Use WrapLayout here
for(int i=0;i<200;i++){
panel1.add(new JButton("b"+i));
}
I have a JLayeredPane. My program works something like this:
JPanel p1 = new JPanel(new BoxLayout(p1, BoxLayout.X_AXIS));
JPanel p2 = new JPanel(new BoxLayout(p2, BoxLayout.Y_AXIS));
JLayeredPane lp = new JLayeredPane();
lp.add(p1, 1);
lp.add(p2, 0);
Both p1 and p2 have components like buttons, etc...
The issue is that when I add both JPanels to the JLayeredPane, NOTHING appears.
I tried changing the layout of the JLayeredPane().
For example, I did:
lp.setLayout(new BoxLayout(lp, BoxLayout.X_AXIS));
Then, the JPanels do show, but they are shown adjacent, not respecting the layers of the JLayeredPane.
Am I forced to use a null layout?
How can I make my JLayeredPane respect the layers and show my two BoxLayout JPanels correctly?
When I give my JLayeredPane a layout, it shows the panels, but it is not respecting the layers at all.
You need a layout manager which understands the Z-Axis. The default layout managers don't understand the Z-Axis of the JLayeredPane.
If you simply want to overlay stuff on top of each other you can use a LayoutManager like this:
JLayeredPane layeredFooPane = new JLayeredPane();
// The magic!
layeredFooPane.setLayout(new LayeredPaneLayout(layeredPane));
// Add components:
layeredFooPane.add(fooComponent, new Integer(JLayeredPane.DEFAULT_LAYER + 10));
layeredFooPane.add(barComponent, JLayeredPane.DEFAULT_LAYER);
LayoutManager Class:
public class LayeredPaneLayout implements LayoutManager {
private final Container target;
private static final Dimension preferredSize = new Dimension(500, 500);
public LayeredPaneLayout(final Container target) {
this.target = target;
}
#Override
public void addLayoutComponent(final String name, final Component comp) {
}
#Override
public void layoutContainer(final Container container) {
for (final Component component : container.getComponents()) {
component.setBounds(new Rectangle(0, 0, target.getWidth(), target.getHeight()));
}
}
#Override
public Dimension minimumLayoutSize(final Container parent) {
return preferredLayoutSize(parent);
}
#Override
public Dimension preferredLayoutSize(final Container parent) {
return preferredSize;
}
#Override
public void removeLayoutComponent(final Component comp) {
}
}
The JLayeredPane acts as if it were using null layout. You must specify your JPanel's size and position when adding it to the JLayeredPane. One of the few times I'll recommend this:
JPanel p1 = new JPanel(new BoxLayout(p1, BoxLayout.X_AXIS));
JPanel p2 = new JPanel(new BoxLayout(p2, BoxLayout.Y_AXIS));
JLayeredPane lp = new JLayeredPane();
lp.add(p1, 1);
lp.add(p2, 0);
p1.setSize(lp.getPreferredSize());
p2.setSize(lp.getPreferredSize());
I was having a similar problem. Instead of passing the layer when you add the panes, try to do it separately:
lp.setLayer(p1, 1);
lp.setLayer(p2, 0);
lp.add(p1);
lp.add(p2);
Layers must be defined before adding components.
You can also try to use JLayeredPane.DEFAULT_LAYER instead of 0.
In my case, my JLayeredPane had GridBagLayout, I don't know if it works with default layout.
I need to display chessboard. I have a BoardPanel class which extends JPanel and a GamePanel (also extending JPanel) class containing BoardPanel. GamePanel fills all the application frame.
I want BoardPanel to always be a square with size equal to the minimum of GamePanel's width and height (if GamePanel's width is greater than height there should be empty space on the left and right, if it's smaller there should be empty space on top and bottom). It's also important that BoardPanel should be displayed in the center of parent panel.
I wrote sth like this:
public GamePanel() {
setLayout(new BorderLayout(0, 0));
boardPanel = new BoardPanel(...);
this.add(boardPanel, BorderLayout.CENTER);
...
}
and in BoardPanel:
public void paintComponent(Graphics g) {
super.paintComponent(g);
int size = Math.min(this.getParent().getHeight(), this.getParent().getWidth());
this.setSize(size, size);
...
}
It resizes well, but chessboard is always displayed in top left corner of GamePanel (all the empty space is displayed on bot or right) and I don't know how to fix it.
Any help? Thanks in advance!
Center it using a GridBagLayout.
import java.awt.*;
import javax.swing.*;
public class CenteredPanel {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
JPanel gui = new JPanel(new GridBagLayout());
JPanel square = new SquarePanel();
square.setBackground(Color.RED);
gui.add(square);
JFrame f = new JFrame("SquareBoard");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);
f.add(gui);
f.setMinimumSize(new Dimension(400,100));
f.pack();
f.setVisible(true);
}
};
SwingUtilities.invokeLater(r);
}
}
class SquarePanel extends JPanel {
#Override
public Dimension getPreferredSize() {
Container c = this.getParent();
int size = Math.min(c.getHeight(), c.getWidth());
Dimension d = new Dimension(size,size);
return d;
}
}
No need for new BorderLayout(0,0) simply use default constructor for BorderLayout
Dont call setSize() rather override getPreferredSize() of JPanel like so:
#Override
public void getPreferredSize() {
int size = Math.min(this.getParent().getHeight(), this.getParent().getWidth());
return new Dimension(size,size);
}
also its never good to do work in your paintComponent as this should be used exclusively for painting only.
If the above does not work I'd suggest a SSCCE to illustrate specific problems you might have