I have a JScrollPane with a very high JPanel inside, that is changed dynamically, items being appended at its end. What I want, is to scroll to the bottom of aforementioned JScrollPane in order for the newly appended items to be visible instantly on addition (they are not appended to the scroll pane directly, but to its JPanel, and are private objects, so cannot be referenced.
How can I simply have that scroll pane scroll to the very bottom?
Thanks in advance!
JComponent.scrollRectToVisible(Rectangle). Call that on the JPanel instance.
E.G.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class ScrollToNewLabel {
public static void main(String[] args) {
SwingUtilities.invokeLater( new Runnable() {
public void run() {
JPanel gui = new JPanel(new BorderLayout(3,3));
final JPanel panel = new JPanel(new GridLayout(0,1));
JScrollPane scroll = new JScrollPane(panel);
scroll.setPreferredSize(new Dimension(80,100));
gui.add(scroll, BorderLayout.CENTER);
JButton addLabel = new JButton("Add Label");
gui.add(addLabel, BorderLayout.NORTH);
ActionListener listener = new ActionListener() {
int counter = 0;
public void actionPerformed(ActionEvent ae) {
panel.add(new JLabel("Label " + ++counter));
panel.revalidate();
int height = (int)panel.getPreferredSize().getHeight();
Rectangle rect = new Rectangle(0,height,10,10);
panel.scrollRectToVisible(rect);
}
};
addLabel.addActionListener(listener);
JOptionPane.showMessageDialog(null, gui);
}
});
}
}
Screen shot
E.G. 2
This e.g. is based on Vincent's answer, to use JScrollPane.getVerticalScrollBar().setValue(height). Where height is the preferred height of the panel in pixels.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class ScrollToNewLabel {
public static void main(String[] args) {
SwingUtilities.invokeLater( new Runnable() {
public void run() {
JPanel gui = new JPanel(new BorderLayout(3,3));
final JPanel panel = new JPanel(new GridLayout(0,1));
final JScrollPane scroll = new JScrollPane(panel);
scroll.setPreferredSize(new Dimension(80,100));
gui.add(scroll, BorderLayout.CENTER);
JButton addLabel = new JButton("Add Label");
gui.add(addLabel, BorderLayout.NORTH);
ActionListener listener = new ActionListener() {
int counter = 0;
public void actionPerformed(ActionEvent ae) {
panel.add(new JLabel("Label " + ++counter));
panel.revalidate();
int height = (int)panel.getPreferredSize().getHeight();
scroll.getVerticalScrollBar().setValue(height);
}
};
addLabel.addActionListener(listener);
JOptionPane.showMessageDialog(null, gui);
}
});
}
}
scrollRectToVisible(...) and scrollBar.setValue(...) are the general solutions.
You may be interested in Scrolling a Form which ensures that when you tab to a component the form will scroll automatically to make sure the the component will be visible in the scrollpane. Behind the scenes it uses scrollRectToVisible().
A simple way to move the scrollbar all the way to the bottom is to set its value to 100 like this:
scroll.getVerticalScrollBar().setValue(100);
This causes it to move to the bottom of the viewport. You can add this after the component is added to the panel.
This is how I scroll down programmatically.
I like how it scrolls to the bottom rather smoothly instead of jumping there immediately.
/**
* Scrolls a {#code scrollPane} to its bottom.
*
* #param scrollPane
* the scrollPane that we want to scroll all the way down
*
*/
private void scrollDown(JScrollPane scrollPane) {
JScrollBar verticalBar = scrollPane.getVerticalScrollBar();
int currentScrollValue = verticalBar.getValue();
int previousScrollValue = -1;
while (currentScrollValue != previousScrollValue) {
// Scroll down a bit
int downDirection = 1;
int amountToScroll = verticalBar.getUnitIncrement(downDirection);
verticalBar.setValue(currentScrollValue + amountToScroll);
previousScrollValue = currentScrollValue;
currentScrollValue = verticalBar.getValue();
}
}
Related
Task: Add some quantity of JLabel with some text to JPanel with BoxLayout and next add this panel to JScrollPane for scrolling down that text labels.
Problem:
I set text.setMaximumSize(..), but JScrollPane don't think so and add horizontal scrollbar. text.setPreferredSize limit perfectly, but i can't use it because need calculate height for each label (maybe use font size and getting label auto line breaker) and it's hard for me.
Here's code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
public class NewPanel {
private JFrame frame;
private JLabel text;
public NewPanel() {
int formWidth = 700;
int formHeight = 700;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++)
sb.append("Test String ");
String resStr = "<html>" + sb.toString() + "</html>";
text = new JLabel();
text.setText(resStr);
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(text);
JScrollPane scrollPane = new JScrollPane(
panel,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
);
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setPreferredSize(new Dimension(formWidth, formHeight));
frame.add(scrollPane);
frame.pack();
//Change max width for label with form resize
frame.addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
super.componentResized(e);
resizePanels();
}
#Override
public void componentMoved(ComponentEvent e) {
super.componentMoved(e);
resizePanels();
}
});
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void resizePanels() {
int labelSize = frame.getWidth() - 200;
// - 200 For show that text limited by setMaximumSize
text.setMaximumSize(new Dimension(labelSize, Integer.MAX_VALUE));
//Integer.MAX_VALUE - for unlimited height (maybe exist better way?)
//text.setPreferredSize(new Dimension(labelSize, 1000));
//limit perfect and hide horizontal scroll, but don't think that it better way
//because i need get plane border "back to back" text border,
//so i must count label height (front height + maybe label auto line break place)
//that look like hard way
text.revalidate();
text.repaint();
}
}
package swingtraining;
import javax.swing.*;
import java.awt.*;
import static java.awt.Color.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import static javafx.scene.paint.Color.TEAL;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
public class MyFrame extends JFrame{
public MyFrame(){
setSize(500,500);
setVisible(true);
setResizable(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
};
public static class MyPanel extends JPanel{
public MyPanel(){
setLayout(new GridBagLayout());
setBackground(BLACK);
setOpaque(true);
GridBagConstraints gbc1 = new GridBagConstraints();
GridBagConstraints gbc2 = new GridBagConstraints();
gbc1.insets = new Insets(200,0,0,200);
gbc1.ipadx = 100;
gbc1.ipady = 100;
gbc1.gridx = 1;
gbc1.gridy = 1;
gbc2.insets = new Insets(0,200,200,0);
gbc2.ipadx = 150;
gbc2.ipady = 10 ;
gbc2.gridx = 1;
gbc2.gridy = 1;
JTextArea jta1 = new JTextArea();
jta1.setLineWrap(true);
jta1.setWrapStyleWord(true);
JButton jb1 = new JButton("Have a banana!");
jb1.setToolTipText("Button prints Banana.");
ActionListener action1 = new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
System.out.println("\nBananas!");
}
};
jb1.addActionListener(action1);
add(jta1,gbc2);
add(jb1,gbc1);
};
};
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
MyFrame jf1 = new MyFrame();
MyPanel jp1 = new MyPanel();
jf1.add(jp1);
}
});
}
}
The code is just a simple JFrame, Panel, with a Button that prints bananas, and a JTextArea. When typed into without the LineWrap etc, it simply extends itself depending on which direction input is being applied. (pressing enter to go down in the area pulls it upwards/downwards, and typing into it pulls it to the left and to the right, making it bigger.) This makes sense, I didn't add a LineWrap or anything. However, adding those;
jta1.setLineWrap(true);
jta1.setWrapStyleWord(true);
I get the following result;
Just a picture to show what I'm talking about:
What I'm aiming for is a JTextArea that has limits, when those are exceeded creates a scrollbar, and doesn't change size at all.
Suggestions?
What I'm aiming for is a JTextArea that has limits, when those are exceeded creates a scrollbar, and doesn't change size at all.
JTextArea jta1 = new JTextArea();
Your text area doesn't have preferred size so it keeps growing. You need to create the text area with a preferred size. This is done by using:
JTextArea jta1 = new JTextArea(5, 30);
Now the layout manager can use the preferred size of the text area and the scrollpane will display the scrollbars when the preferred size of the text area is greater than the size of the scroll pane.
when those are exceeded creates a scrollbar, and doesn't change size at all.
And as pointed out by MadProgrammer if you want a scrollbar then you also need to actually add your text area to a JScrollPane and then add the scrollpane to the frame. So you would also need to use code like:
//add(jta1,gbc2);
add(new JScrollPane(jta1), gbc);
Read the section from the Swing tutorial on How to Use Text Areas for more information and examples.
I have a panel with layout (for example, BorderLayout) and a JScrollPane on its center. JScrollPane has content inside it (a JPanel)
The thing is, that when this JScrollPane resizes, I do not want its content to resize. For example, if layout increases the JScrollPane, I want its content to be as small it was (and occupy only part of the pane), but it resizes to fit pane.
I also need an opportunity to reduce content inside the pane and increase it manually (there is no problems with increasing, they are in reducing).
So, how can I achieve content size independency? Of course, I need to save scrolling features, if content will be bigger than JScrollPane.
Here is a simple example:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TestScroll extends JFrame {
public TestScroll() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(500, 500);
init();
}
private void init() {
setLayout(new BorderLayout());
// Inner panel
final JPanel innerPanel = new JPanel();
innerPanel.setOpaque(true);
innerPanel.setBackground(Color.BLUE);
innerPanel.setPreferredSize(new Dimension(200, 200));
// Scroll
final JScrollPane scrollPane = new JScrollPane(innerPanel);
add(scrollPane, BorderLayout.CENTER);
// Buttons
JPanel buttonPanel = new JPanel();
JButton extendButton = new JButton("Extend inner panel");
extendButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
innerPanel.setPreferredSize(new Dimension(innerPanel.getWidth() * 2,
innerPanel.getHeight() * 2));
innerPanel.revalidate();
}
});
buttonPanel.add(extendButton);
JButton reduceButton = new JButton("Reduce inner panel");
reduceButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
innerPanel.setPreferredSize(new Dimension(innerPanel.getWidth() / 2,
innerPanel.getHeight() / 2));
innerPanel.revalidate();
}
});
buttonPanel.add(reduceButton);
add(buttonPanel, BorderLayout.NORTH);
setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new TestScroll();
}
});
}
}
So, problem statement:
I do not want inner panel to stretch to the pane (but pane can be resized by outer layout, so the panel must just keep its size).
I want to be able to reduce inner panel manually so it can occupy only a part of the scroll pane.
And, of course, I want to save scrolling functionality when inner panel is larger than scroll pane.
Did you follow Andrew's link about using layout managers to achieve your goal?
Here is another simple example:
//final JScrollPane scrollPane = new JScrollPane(innerPanel);
JPanel outer = new JPanel();
outer.add( innerPanel );
final JScrollPane scrollPane = new JScrollPane(outer);
i have a JScrollPane.
But default it displays JTextArea.
JTextArea jTextArea = new JTextArea();
JScrollPane pane = new JScrollPane(jTextArea);
so here everything is fine. But now i would like to change JScrollPane component by user action:
pane.remove(jTextArea);
pane.add(new JTable(data[][], columns[]));
pane.revalidate();
frame.repaint();
frame in my main Window. JScrollPane is added to main window with GridBagLayout.
But this doesn't work. After running action JScrollPane becomes grey.
jScrollPane.getViewport().remove/add
One alternative is to put a JPanel with a CardLayout1 into the JScrollPane, add both the components to the panel, then simply flip between the text area and table as needed.
How to Use CardLayout
Given the components might be of vastly different size, it might be better to do it this way:
JPanel with CardLayout contains many JScrollPane instances, each of which contains a single component. That will also work inherently better for a JTable.
Edited my answer after receiving one valuable suggestion by His Majesty #camickr, setViewportView(componentObject); is used to do such things.
A sample code to help your cause :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ScrollPaneExample extends JFrame
{
private JPanel panel;
private JScrollPane scrollPane;
private JTextArea tarea;
private JTextPane tpane;
private JButton button;
private int count;
public ScrollPaneExample()
{
count = 0;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationByPlatform(true);
panel = new JPanel();
panel.setLayout(new BorderLayout());
tarea = new JTextArea();
tarea.setBackground(Color.BLUE);
tarea.setForeground(Color.WHITE);
tarea.setText("TextArea is working");
scrollPane = new JScrollPane(tarea);
tpane = new JTextPane();
tpane.setText("TextPane is working.");
button = new JButton("Click me to CHANGE COMPONENTS");
button.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
if (count == 0)
{
scrollPane.setViewportView(tpane);
count++;
}
else if (count == 1)
{
scrollPane.setViewportView(tarea);
count--;
}
}
});
setContentPane(panel);
panel.add(scrollPane, BorderLayout.CENTER);
panel.add(button, BorderLayout.PAGE_END);
pack();
setVisible(true);
}
public static void main(String... args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new ScrollPaneExample();
}
});
}
}
Hope this might help you in some way.
Regards
I need to add components dynamically. Moreover, I need to alter the layout dynamically.
For reference, here's an sscce that shows the essential method, validate(). This more elaborate example shows both requirements: it changes the layout and adds components dynamically.
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
/** #see http://stackoverflow.com/questions/5750068 */
public class DynamicLayout extends JPanel {
private static final LayoutManager H = new GridLayout(1, 0);
private static final LayoutManager V = new GridLayout(0, 1);
public DynamicLayout() {
this.setLayout(H);
this.setPreferredSize(new Dimension(320, 240));
for (int i = 0; i < 3; i++) {
this.add(new JLabel("Label " + String.valueOf(i), JLabel.CENTER));
}
}
private void display() {
JFrame f = new JFrame("DynamicLayout");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
JPanel p = new JPanel();
p.add(new JButton(new AbstractAction("Horizontal") {
#Override
public void actionPerformed(ActionEvent e) {
DynamicLayout.this.setLayout(H);
DynamicLayout.this.validate();
}
}));
p.add(new JButton(new AbstractAction("Vertical") {
#Override
public void actionPerformed(ActionEvent e) {
DynamicLayout.this.setLayout(V);
DynamicLayout.this.validate();
}
}));
f.add(p, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new DynamicLayout().display();
}
});
}
}
Example of changing the layout Dynamically :
package swinglayout;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class LayoutChanger implements ActionListener{
JButton b1;
JButton b2;
JButton b3;
JButton b4;
JButton b5;
JButton b6;
/** This button set the flowlayout on panel2 with left orientation */
JButton flowLayout;
/** This button set the Gridlayout of 2,3 grid on panel2 */
JButton gridLayout;
/** This button set the Borderlayout on panel2*/
JButton borderLayout;
/**
* This panel is control panel where we use button to change
* layout of another panel
*/
JPanel panel;
/** This panel contain multiple button from b1 to b6 */
JPanel panel2;
JFrame frame;
public LayoutChanger() {
//set Default Look and Feel on frame
JFrame.setDefaultLookAndFeelDecorated(true);
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container con = frame.getContentPane();
con.setLayout(new BorderLayout());
panel = new JPanel();
panel2 = new JPanel();
//This button are used to only showing the layout effect
b1 = new JButton("HelloButton1");
b2 = new JButton("HelloButton2");
b3 = new JButton("HelloButton3");
b4 = new JButton("HelloButton4");
b5 = new JButton("HelloButton5");
b6 = new JButton("HelloButton6");
// By default panel have layout
panel2.add(b1);
panel2.add(b2);
panel2.add(b3);
panel2.add(b4);
panel2.add(b5);
panel2.add(b6);
// Layout changer button
flowLayout = new JButton("FlowLayout");
gridLayout = new JButton("GridLayout");
borderLayout = new JButton("BorderLayout");
//call Action listener on every layout changer button
flowLayout.addActionListener(this);
gridLayout.addActionListener(this);
borderLayout.addActionListener(this);
panel.add(flowLayout);
panel.add(gridLayout);
panel.add(borderLayout);
// add layout changer button panel at a top
//button panel at the center of container
con.add(panel, BorderLayout.PAGE_START);
con.add(panel2, BorderLayout.CENTER);
frame.setVisible(true);
frame.pack();
}
public void actionPerformed(ActionEvent e) {
//set the flowlayout on panel2
if(e.getSource() == flowLayout) {
FlowLayout flow = new FlowLayout(FlowLayout.LEFT);
panel2.setLayout(flow);
panel2.validate();
}
//set the gridlayout on panel2
if(e.getSource() == gridLayout) {
GridLayout grid = new GridLayout(2,3);
panel2.setLayout(grid);
panel2.validate();
}
//set the gridlayout but the problem if we don't set the constraint
//all button are set on center. So you remove the all button from panel
//Then set grid layout on panel and add them with constraints.
if(e.getSource() == borderLayout) {
panel2.remove(b1);
panel2.remove(b2);
panel2.remove(b3);
panel2.remove(b4);
panel2.remove(b5);
panel2.remove(b6);
BorderLayout border = new BorderLayout();
panel2.setLayout(border);
panel2.add(b1,BorderLayout.NORTH);
panel2.add(b2,BorderLayout.SOUTH);
panel2.add(b3,BorderLayout.EAST);
panel2.add(b4,BorderLayout.WEST);
panel2.add(b5,BorderLayout.CENTER);
panel2.add(b6,BorderLayout.BEFORE_FIRST_LINE);
panel2.validate();
}
}
public static void main(String args[]) {
new LayoutChanger();
}
}
One thing remember is you set new layout on panel don't forgot call the method validate() on panel.If you don't call this method you don't see the effect of change in layout. If you want to see the effect with out call the method you must resize the frame.
Also you can set same layout very easily like FlowLayout,GridLayout and BoxLayout, but when set BorderLayout it required constraints for adding element, so we first remove all component from panel by remove(Component comp) method then add the component in panel by constraint