JScrollPane adding JPanels at the top and keeping current scroll view - java

I have a JScrollPane that contains a vertical Box. I'm inserting new JPanel's at the top of Box. If I use the scrollbar to scroll down I'd like for the current view to remain where I scrolled down to. For example, if I have 50 panels in the box and use the scrollbar to view panel 20, I'd like the view to remain on box 20 even though other boxes are added on top. Additionally, if I use the scrollbar to scroll back up to the top I'd like the view to display new panels as they are added. Any idea how to do this?
BTW, it isn't necessary to use a JScrollPane or a Box. The example code is just to help explain what I am trying to do.
Example code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TestScrollPane extends JFrame {
JScrollPane scrollPane;
Box box;
private static int panelCount = 0;
public TestScrollPane() {
setPreferredSize(new Dimension(200, 400));
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
scrollPane = new JScrollPane();
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.getVerticalScrollBar().setUnitIncrement(15);
box = Box.createVerticalBox();
scrollPane.getViewport().add(box);
this.add(scrollPane);
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
Timer t = new Timer(500, new ActionListener() {
public void actionPerformed(ActionEvent ae) {
box.add(new TestPanel(), 0);
scrollPane.validate();
}
});
t.setRepeats(true);
t.start();
}
public class TestPanel extends JPanel {
int myId = panelCount++;
public TestPanel() {
this.setLayout(new GridBagLayout());
this.setBorder(BorderFactory.createBevelBorder(1));
JLabel label = new JLabel("" + myId);
label.setHorizontalAlignment(JLabel.CENTER);
label.setVerticalAlignment(JLabel.CENTER);
this.setMaximumSize(new Dimension(100, 100));
this.setPreferredSize(new Dimension(100, 100));
this.add(label);
}
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
TestScrollPane testScrollPane = new TestScrollPane();
}
});
}
}
EDIT:
This is how I ended up changing the code. I feel somewhat foolish for not seeing the obvious. Anyways, thanx to those that helped.
public void actionPerformed(ActionEvent ae) {
Point view = scrollPane.getViewport().getViewPosition();
TestPanel panel = new TestPanel();
box.add(panel, 0);
scrollPane.validate();
if (view.y != 0) {
view.y += panel.getHeight();
scrollPane.getViewport().setViewPosition(view);
}
}
BTW, I had cross posted this question to http://www.coderanch.com/t/528829/GUI/java/JScrollPane-adding-JPanels-at-top#2398276 Just FYI for those that might care.

You could get the bounds of the component you want to make visible (using JComponent's getBounds method), and use that as an input to JViewPort's scrollRectToVisible method.

Something like:
Timer t = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent ae) {
TestPanel panel = new TestPanel();
box.add(panel, 0);
JViewport vp = scrollPane.getViewport();
Point p = vp.getViewPosition();
p.y += panel.getPreferredSize().height;
scrollPane.revalidate();
vp.setViewPosition(p);
}
});

Related

Java Swing: Components automatically change position when a certain component updates

I made a Java program and part of the program's function is to track the user's mouse X and Y coordinates.
The tracking works nicely but there's a small problem that bothers me.
When I move my mouse around the screen, the other components automatically change position.
Here's a MRE(Minimal Reproducible Example):
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.event.ActionListener;
public class Test {
static Timer t;
static JLabel label1;
static InnerTest inner;
static int mouseX;
static int mouseY;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
buildFrame();
runTimer();
}
});
}
public static void buildFrame() {
JFrame frame = new JFrame();
label1 = new JLabel("Test1");
JLabel label2 = new JLabel("Test Test");
JLabel label3 = new JLabel("Test Label Label");
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(label1);
panel.add(label2);
panel.add(label3);
label1.setAlignmentX(Component.CENTER_ALIGNMENT);
label2.setAlignmentX(Component.CENTER_ALIGNMENT);
label3.setAlignmentX(Component.CENTER_ALIGNMENT);
frame.getContentPane().add(panel);
frame.setSize(400, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void runTimer() {
inner = new InnerTest();
t = new Timer(20, inner);
t.start();
}
static class InnerTest implements ActionListener {
public void actionPerformed(ActionEvent e) {
mouseX = MouseInfo.getPointerInfo().getLocation().x * 100;
mouseY = MouseInfo.getPointerInfo().getLocation().y * 100;
label1.setText(" ( "+String.valueOf(mouseX)+" , "+String.valueOf(mouseY)+" )");
}
}
}
How do I keep the components still when another component is updating?
The components are added to the same panel so each is centered based on the maximum width of all three components. As the width of the top label changes the others are also adjusted.
The solution is to separate the top label from the other two labels.
One way would be:
//panel.add(label1);
panel.add(label2);
panel.add(label3);
//label1.setAlignmentX(Component.CENTER_ALIGNMENT);
label1.setHorizontalAlignment(JLabel.CENTER); // added
label2.setAlignmentX(Component.CENTER_ALIGNMENT);
label3.setAlignmentX(Component.CENTER_ALIGNMENT);
frame.add(label1, BorderLayout.PAGE_START); // added
frame.getContentPane().add(panel);

JScrollPane does not react to scrollRectToVisible

I have the following window:
public class MyWindows extends JFrame {
private final JScrollPane pane;
public MyWindows(){
super();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
JPanel panel = new JPanel();
pane = new JScrollPane(panel);
pane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
JButton left = new JButton("<");
left.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
moveLeft();
}
});
cp.add(left, BorderLayout.WEST);
panel.setLayout(new GridLayout(1,0));
for(int i = 1; i<20; i++) {
panel.add(new JButton("hallo nummer "+i));
}
cp.add(pane, BorderLayout.CENTER);
JButton right = new JButton(">");
cp.add(right, BorderLayout.EAST);
this.setLocationRelativeTo(null);
pack();
this.setSize(300, 100);
}
private void moveLeft() {
Rectangle rec = pane.getVisibleRect();
rec.setLocation((int)(rec.getX()+1000), (int)rec.getY());
System.out.println(rec);
pane.scrollRectToVisible(rec);
System.out.println(pane.getVisibleRect());
}
}
The idea was to scroll along the buttons in the center, using the buttons on the left and on the right.
Unfortunately, the moveLeft()-Method does exactly nothing when it comes to scrolling.
The target-rectangle is java.awt.Rectangle[x=1000,y=0,width=202,height=61]
To me, that looks like a rectangle the ScrollPane should be able to scroll to.
What am I missing?
Also, sorry about the wall of code, but I just have no idea where the error may be.
Call scrollRectToVisible method on desired component (JPanel in your case) but on JScrollPane object.
private JPanel panel;
...
private void moveLeft() {
Rectangle rec = panel.getVisibleRect();
rec.setLocation((int) (rec.getX() + 1000), (int) rec.getY());
System.out.println(rec);
panel.scrollRectToVisible(rec);
System.out.println(panel.getVisibleRect());
}

What is the correct way to make a JScrollPane scroll to the bottom after a component has been inserted?

I have seen this question a couple of times, but the answers I found are a bit "bad" in my opinion.
So, basically I have a JScrollPane that I insert components to. Each time I insert a component, I want the JScrollPane to scroll to the bottom. Simple enough.
Now, the logical thing to do would be to add a listener (componentAdded) to the container that I am inserting to.
That listener would then simply scroll to the bottom. However, this will not work, as the component height has not been finished calculating at this time, thus the scrolling fails.
The answers I have seen to this usually involves putting the scroll-row in one (or even several chained) "invokeLater" threads.
This seems to me like an "ugly hack". Surely there should be a better way to actually move the scroll once all the height calculations are done, instead of just "delaying" the scroll for a unknown amount of time?
I also read some answers that you should work with the SwingWorker, which I never really understood. Please enlighten me :)
Here is some code for you to modify (read "make work"):
JScrollPane scrollPane = new JScrollPane();
add(scrollPane);
JPanel container = new JPanel();
scrollPane.setViewportView(container);
container.addContainerListener(new ContainerAdapter() {
public void componentAdded(ContainerEvent e) {
JScrollPane scrollPane = (JScrollPane) value.getParent().getParent();
JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
scrollBar.setValue(scrollBar.getMaximum());
}
});
JPanel hugePanel = new JPanel();
hugePanel.setPreferredSize(new Dimension(10000, 10000);
container.add(hugePanel);
UPDATE:
Added some code to test the theory. However, it seems to work fine, so I guess I have a problem somewhere else in my program :)
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ContainerAdapter;
import java.awt.event.ContainerEvent;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.LineBorder;
public class ScrollTest extends JFrame {
private static final long serialVersionUID = -8538440132657016395L;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ScrollTest().setVisible(true);
}
});
}
public ScrollTest() {
UIManager.put("swing.boldMetal", Boolean.FALSE);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("Scroll Test");
setSize(1000, 720);
setLocationRelativeTo(null);
JPanel container = new JPanel();
container.setLayout(new BoxLayout(container, BoxLayout.X_AXIS));
add(container);
//Create 3 scollpanels
final JScrollPane scrollPane1 = new JScrollPane();
scrollPane1.setBorder(new LineBorder(Color.RED, 2));
container.add(scrollPane1);
final JScrollPane scrollPane2 = new JScrollPane();
scrollPane2.setBorder(new LineBorder(Color.GREEN, 2));
container.add(scrollPane2);
final JScrollPane scrollPane3 = new JScrollPane();
scrollPane3.setBorder(new LineBorder(Color.BLUE, 2));
container.add(scrollPane3);
//Create a jpanel inside each scrollpanel
JPanel wrapper1 = new JPanel();
wrapper1.setLayout(new BoxLayout(wrapper1, BoxLayout.Y_AXIS));
scrollPane1.setViewportView(wrapper1);
wrapper1.addContainerListener(new ContainerAdapter() {
public void componentAdded(ContainerEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
scrollPane1.getVerticalScrollBar().setValue(scrollPane1.getVerticalScrollBar().getMaximum());
}
});
}
});
JPanel wrapper2 = new JPanel();
wrapper2.setLayout(new BoxLayout(wrapper2, BoxLayout.Y_AXIS));
scrollPane2.setViewportView(wrapper2);
wrapper2.addContainerListener(new ContainerAdapter() {
public void componentAdded(ContainerEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
scrollPane2.getVerticalScrollBar().setValue(scrollPane2.getVerticalScrollBar().getMaximum());
}
});
}
});
JPanel wrapper3 = new JPanel();
wrapper3.setLayout(new BoxLayout(wrapper3, BoxLayout.Y_AXIS));
scrollPane3.setViewportView(wrapper3);
wrapper3.addContainerListener(new ContainerAdapter() {
public void componentAdded(ContainerEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
scrollPane3.getVerticalScrollBar().setValue(scrollPane3.getVerticalScrollBar().getMaximum());
}
});
}
});
//Add come stuff into each wrapper
JPanel junk;
for(int x = 1; x <= 1000; x++) {
junk = new JPanel();
junk.setBorder(new LineBorder(Color.BLACK, 2));
junk.setPreferredSize(new Dimension(100, 40));
junk.setMaximumSize(junk.getPreferredSize());
wrapper1.add(junk);
}
for(int x = 1; x <= 1000; x++) {
junk = new JPanel();
junk.setBorder(new LineBorder(Color.BLACK, 2));
junk.setPreferredSize(new Dimension(100, 40));
junk.setMaximumSize(junk.getPreferredSize());
wrapper2.add(junk);
}
for(int x = 1; x <= 1000; x++) {
junk = new JPanel();
junk.setBorder(new LineBorder(Color.BLACK, 2));
junk.setPreferredSize(new Dimension(100, 40));
junk.setMaximumSize(junk.getPreferredSize());
wrapper3.add(junk);
}
}
}
The correct - that is without wasting time in re-inventing the wheel - way to scroll a component anywhere you want it is something like:
wrapper1.addContainerListener(new ContainerAdapter() {
#Override
public void componentAdded(final ContainerEvent e) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JComponent comp = (JComponent) e.getChild();
Rectangle bounds = new Rectangle(comp.getBounds());
comp.scrollRectToVisible(bounds);
}
});
}
});
The smells you are avoiding:
no reference to any enclosing parent (like the scrollPane)
no digging into the internals of any parent (like the scrollBar)
no need for a custom model
As to the invokeLater, citing its api doc (bolding added by me):
Causes doRun.run() to be executed asynchronously on the AWT event
dispatching thread. This will happen after all pending AWT events have
been processed
So you can be fairly certain that the internals are handled before your code is executed.

How to change the color of a JSplitPane

I have written a small program, while reading a book about swing, that creates a JSplitPane between two labels.
The problem is that the JSplitPane can barely be seen (at least in my operating system - MAC OS Lion) and setting some properties on it (like foreground color) does not seem to work.
Here is the code :
//Demonstrate a simple JSplitPane
package swingexample4_6;
import javax.swing.*;
import java.awt.*;
public class SplitPaneDemo {
//constructor
public SplitPaneDemo()
{
//Create a new JFrame container.
//Use the default border layout
JFrame jfrm = new JFrame("Split Pane Demo");
//Give the frame an initial size
jfrm.setSize(380, 150);
//Terminate the program when the user closes the application
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//--Make two labels to show the split pane
JLabel jlab = new JLabel(" Left side: ABCDEFGHIJKLMNOPQRSTUVWXYZ");
JLabel jlab2 = new JLabel(" Right side: ABCDEFGHIJKLMNOPQRSTUVWXYZ");
//Set the minimum size for each label
//This step is not technically needed to use a split pane,
//but it enables the split pane resizing features to be
//used to their maximum extent
jlab.setMinimumSize(new Dimension(90, 30));
jlab2.setMinimumSize(new Dimension(90, 30));
//--Create a split pane
JSplitPane jsp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, jlab, jlab2);
//Code to get a list of component names in the console
Component[] listComponents = jsp.getComponents();
String theList;
for (Component myComponent: listComponents)
{
theList = myComponent.toString();
System.out.println(theList);
}
//Add the split pane to the content pane
jfrm.getContentPane().add(jsp);
//Display the frame
jfrm.setVisible(true);
}
public static void main(String[] args) {
//Create the frame on the event dispatching thread
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
new SplitPaneDemo();
}
});
}
}
Is there any way I can change its color , so that it can really stand out?
Thank you.
You can use the SplitPane.background property, as shown below.
import javax.swing.*;
import java.awt.*;
/** #see http://stackoverflow.com/a/10110232/230513 */
public class SplitPaneDemo {
//constructor
public SplitPaneDemo() {
JFrame jf = new JFrame("Split Pane Demo");
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//--Make two labels to show the split pane
JPanel left = content("Left side: ");
JPanel right = content("Right side: ");
//--Create a split pane
JSplitPane jsp = new JSplitPane(
JSplitPane.HORIZONTAL_SPLIT, true, left, right);
jsp.setDividerLocation(0.5f);
//Add the split pane to the frame's content pane
jf.add(jsp);
jf.pack();
//Display the frame
jf.setLocationRelativeTo(null);
jf.setVisible(true);
//Code to get a list of component names in the console
for (Component myComponent : jsp.getComponents()) {
System.out.println(myComponent);
}
}
private JPanel content(String s) {
final JLabel label = new JLabel(s + "Some text.", JLabel.CENTER);
JPanel panel = new JPanel(new GridLayout()) {
#Override
public Dimension getPreferredSize() {
Dimension d = label.getPreferredSize();
return new Dimension(d.width * 2, d.height * 3);
}
};
panel.setOpaque(true);
panel.setBackground(new Color(0xffffffc0));
panel.add(label);
return panel;
}
public static void main(String[] args) {
UIManager.put("SplitPane.background", new Color(0xff8080ff));
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new SplitPaneDemo();
}
});
}
}
JLabel is by default NON_Opaque, simple is transparent, you can
change JLabels to the JComponent or JPanel could be better
change opacity by JLabel#setOpaque(true)

How can I properly center a JPanel ( FIXED SIZE ) inside a JFrame?

Hi all!
I'm trying to solve an -apparently- simple problem, but I cannot fix it.
I'm working on a sample application with Java/Swing libraries;
I have a JFrame and a JPanel.
I just want to achieve the following objectives:
JPanel MUST be centered inside the JFrame.
JPanel MUST have ALWAYS the size that is specified with
setPreferredSize() method. It MUST NOT be resized under this size.
I tried by using a GridBagLayout: it's the ONLY way I can do it.
See the sample below:
/* file StackSample01.java */
import java.awt.*;
import javax.swing.*;
public class StackSample01 {
public static void main(String [] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.setPreferredSize(new Dimension(100, 100));
panel.setBackground(Color.RED);
frame.setLayout(new GridBagLayout());
frame.add(panel, new GridBagConstraints());
frame.setSize(new Dimension(200, 200));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Here a screenshot:
I would not use a GridBagLayout to do a thing too simple.
I tried a simplest solution, by using a Box, but this does not work:
Sample code:
/* file StackSample02.java */
import java.awt.*;
import javax.swing.*;
public class StackSample02 {
public static void main(String [] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.setPreferredSize(new Dimension(100, 100));
panel.setBackground(Color.RED); // for debug
panel.setAlignmentX(JComponent.CENTER_ALIGNMENT); // have no effect
Box box = new Box(BoxLayout.Y_AXIS);
box.add(Box.createVerticalGlue());
box.add(panel);
box.add(Box.createVerticalGlue()); // causes a deformation
frame.add(box);
frame.setSize(new Dimension(200, 200));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Here a screenshot,
Any ideas? Thanks to all :-)
BoxLayout can pretty to hold your setXxxSize(), then just add panel.setMaximumSize(new Dimension(100, 100));
and your output would be
Removed by setMinimumSize(notice if Container has greater size as ... )
import java.awt.*;
import javax.swing.*;
public class CustomComponent12 extends JFrame {
private static final long serialVersionUID = 1L;
public CustomComponent12() {
Box box = new Box(BoxLayout.Y_AXIS);
box.setAlignmentX(JComponent.CENTER_ALIGNMENT);
box.add(Box.createVerticalGlue());
box.add(new CustomComponents12());
box.add(Box.createVerticalGlue());
add(box);
pack();
setTitle("Custom Component Test / BoxLayout");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setMaximumSize(getMinimumSize());
setMinimumSize(getMinimumSize());
setPreferredSize(getPreferredSize());
setLocation(150, 150);
setVisible(true);
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
CustomComponent12 main = new CustomComponent12();
}
};
javax.swing.SwingUtilities.invokeLater(r);
}
}
class CustomComponents12 extends JPanel {
private static final long serialVersionUID = 1L;
#Override
public Dimension getMinimumSize() {
return new Dimension(100, 100);
}
#Override
public Dimension getMaximumSize() {
return new Dimension(100, 100);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
#Override
public void paintComponent(Graphics g) {
int margin = 10;
Dimension dim = getSize();
super.paintComponent(g);
g.setColor(Color.red);
g.fillRect(margin, margin, dim.width - margin * 2, dim.height - margin * 2);
}
}
First of all, thanks to all.
I reply another time to my own question, to show everyone the choice I have made.
See the sample code below;
As you can see, I have included only minimal steps which are absolutely necessary to achieve the goal.
/* file StackResponse.java */
import java.awt.*;
import javax.swing.*;
public class StackResponse {
public static void main(String [] args) {
JPanel panel = new JPanel();
Dimension expectedDimension = new Dimension(100, 100);
panel.setPreferredSize(expectedDimension);
panel.setMaximumSize(expectedDimension);
panel.setMinimumSize(expectedDimension);
panel.setBackground(Color.RED); // for debug only
Box box = new Box(BoxLayout.Y_AXIS);
box.add(Box.createVerticalGlue());
box.add(panel);
box.add(Box.createVerticalGlue());
JFrame frame = new JFrame();
frame.add(box);
frame.setSize(new Dimension(200, 200));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setMinimumSize(frame.getMinimumSize()); // cannot be resized-
frame.setVisible(true);
}
}
Here you can see a screenshot.
Problem solved.
Many thanks again to all.
IT
create a panel by name "FixedPanel" with GridBagLayout and set preferred size to frame size
then add your frame into FixedPanel.
Frame = new JFrame("CenterFrame");
Frame.setLocation(0, 0);
Frame.setSize(new Dimension(400,400));//dim
JPanel FixedPanel = new JPanel(new GridBagLayout());
FixedPanel.setPreferredSize(Frame.getSize());
JPanel myPanel = new JPanel();
myPanel.setPreferredSize(new Dimension(100,100));
myPanel.setBackground(Color.BLACK);
FixedPanel.add(myPanel);
Frame.add(FixedPanel);
Frame.setVisible(true);
You can do this. I had to make a chess game, and I wanted the chess piece piece to always go in the center of a cell which was a JlayeredPane:
private void formMouseReleased(java.awt.event.MouseEvent evt) {
// TODO add your handling code here:
if (jl != null)
{
jl.setLocation(evt.getX()+10, evt.getY()+10);
Component com = findComponentAt(evt.getPoint());
if (com instanceof JPanel)
{
// System.out.println("Yes, it's a jpanel");
((JPanel)com).add(jl);
((JPanel)com).validate();
}
}
}
Its Just Having
jPanel.setBounds(x, y, 1046, 503);
Where x is space for right side and y is space for left side.
you have to calculate the space from both side according to screen height and width
use
panel.setMaximumSize(new Dimension(200,200));
panel.setResizable(false)
instead?

Categories

Resources