How to Make Jscrollpane (In Borderlayout, Containing JPAnel) Smoothly Autoscroll

How to make JScrollPane (In BorderLayout, containing JPanel) smoothly autoscroll

You can set the value of the horizontal scrollbar to control what is currently visible:

JScrollBar horizontal = scroll.getHorizontalScrollBar();
horizontal.setValue( horizontal.getValue() + ??? );

You would need to use a Swing Timer to schedule the scrolling at an appropriate interval.

Simple example of using a Timer to scroll text:

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

public class TimerTest extends JPanel implements ActionListener
{
JLabel timeLabel;
JLabel scrollLabel;

public TimerTest()
{
setLayout( new BorderLayout() );

timeLabel = new JLabel( new Date().toString() );
add(timeLabel, BorderLayout.NORTH);

scrollLabel = new JLabel( "Some continuously scrolling text!! " );
add(scrollLabel, BorderLayout.SOUTH);

int time = 1000;
javax.swing.Timer timer = new javax.swing.Timer(time, this);
timer.setInitialDelay(1);
timer.start();
}

public void actionPerformed(ActionEvent e)
{
timeLabel.setText( new Date().toString() );
String oldText = scrollLabel.getText();

// Scroll right to left
String newText = oldText.substring(1) + oldText.substring(0, 1);

// Scroll left to right
// int length = oldText.length();
// String newText = oldText.substring(length-1, length)
// + oldText.substring(0, length-1);

scrollLabel.setText( newText );
}

private static void createAndShowGUI()
{
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new TimerTest() );
frame.setLocationByPlatform( true );
frame.pack();
frame.setVisible( true );
}

public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}

How to continuously scroll with JScrollPane

Check out the setAutoScrolls(...) method of the JComponent class.

You can just use:

panel.setAutoScrolls( true );

And then you use the following MouseMotionListener:

 MouseMotionListener doScrollRectToVisible = new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
Rectangle r = new Rectangle(e.getX(), e.getY(), 1, 1);
((JPanel)e.getSource()).scrollRectToVisible(r);
}
};
panel.addMouseMotionListener(doScrollRectToVisible);

This concept is demonstrated in the ScollDemo example found in the Swing tutorial on How to Use Scroll Panes.

Java move GUI panel While loop

We meet again!

I managed to implement the moving JPanel using a Swing timer.

Since you want to move the JPanel, you need to place it in a parent container that is large enough to allow the JPanel to be placed in different locations within it. You also need to use a null layout manager because most layout managers will ignore calls to method setLocation().

More explanations after the code.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.WindowConstants;

public class MovePane implements ActionListener, Runnable {
private static final String START = "Start";
private static final String STOP = "Stop";

private int diff;
private JButton startButton;
private JButton stopButton;
private JFrame frame;
private JPanel jPanel1;
private Timer timer;

public MovePane() {
diff = 10;
}

@Override // jva.awt.event.ActionListener
public void actionPerformed(ActionEvent event) {
String actionCommand = event.getActionCommand();
switch (actionCommand) {
case START:
launchTimer();
break;
case STOP:
stopTimer();
break;
default:
JOptionPane.showMessageDialog(frame,
actionCommand,
"Unhandled",
JOptionPane.WARNING_MESSAGE);
}
}

@Override // java.lang.Runnable
public void run() {
showGui();
}

private JButton createButton(String text, int mnemonic, String tooltip, boolean enabled) {
JButton button = new JButton(text);
button.setMnemonic(mnemonic);
button.setToolTipText(tooltip);
button.setEnabled(enabled);
button.addActionListener(this);
return button;
}

private JPanel createButtonsPanel() {
JPanel buttonsPanel = new JPanel();
startButton = createButton(START, KeyEvent.VK_M, "Sets panel in motion.", true);
buttonsPanel.add(startButton);
stopButton = createButton(STOP, KeyEvent.VK_P, "Stops panel motion.", false);
buttonsPanel.add(stopButton);
return buttonsPanel;
}

private JPanel createMainPanel() {
JPanel mainPanel = new JPanel(null);
mainPanel.setPreferredSize(new Dimension(400, 400));
jPanel1 = new JPanel();
jPanel1.setBounds(10, 200, 50, 50);
jPanel1.setBorder(BorderFactory.createLineBorder(Color.RED, 2, true));
mainPanel.add(jPanel1);
return mainPanel;
}

private void launchTimer() {
if (timer == null) {
timer = new Timer(0, e -> movePanel());
timer.setDelay(500);
}
timer.start();
startButton.setEnabled(false);
stopButton.setEnabled(true);
}

private void stopTimer() {
timer.stop();
startButton.setEnabled(true);
stopButton.setEnabled(false);
}

private void movePanel() {
Point location = jPanel1.getLocation();
if (location.x > 180) {
diff = -10;
}
else if (location.x < 10) {
diff = 10;
}
location.x += diff;
jPanel1.setLocation(location);
}

private void showGui() {
frame = new JFrame("Move");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(createMainPanel(), BorderLayout.CENTER);
frame.add(createButtonsPanel(), BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}

public static void main(String[] args) {
EventQueue.invokeLater(new MovePane());
}
}

Swing is event driven. Events are added to a queue and then read off the queue and performed. Setting the location of a JPanel is an event. Calling events in a loop just floods the queue with events and some may be lost. That's why there is the Swing Timer class. In the above code, every half second I change the location of jPanel1.

Animating a Rectangle in a specific pattern

What you have done so far looks pretty good. You only need your action method to work properly. For that i would use a class variable called direction:

private boolean direction = true;

Now in your action method you move the rectangle either forward or backwards, depending on direction. And if it hits the end you move the rectangle down and invert direction:

public void actionPerformed(ActionEvent e) {

if (direction){
moveForward();
}
else {
moveBackwards();
}

//Check if it is at the end
if(((x >= 270) && (direction)) || ((x <= 30) && (!direction))) {
moveDown();
direction = !direction;
}
}

The if clause is a little bit complicated, but you can split it up, if you want it to be more readable.

JScrollPane doesn't truncate JLabel text

When I add the JPanel to the Frame it truncates the JLabels text, if it exceeds the bounds of the frame

Correct, because the layout manager forces the components to be sized in the space available.

However when I place the JPanel with the rows inside the JScrollPane it doesn't truncate the JLabel text, which then exceeds the JFrame

Correct, because the point of using a JScrollPane is to allow each component to be displayed at its preferred size. If the component size doesn't fit then scrolling will result.

If you want to control the width of the panel to fit the width of the scrollpane then you need to implement the Scrollable interface on your panel and override the getScrollableTracksViewportWidth() method to return Boolean.TRUE. Read the API for more information about the Scrollable interface.

An easy way to do this is to use the Scrollable Panel which has methods to allow you to control the scrollable properties.

Scroll JScrollPane by dragging mouse (Java swing)

Okay, that ended up been much simpler then I though it would be...

First, don't mess with the JViewport, instead, use JComponent#scrollRectToVisible directly on the component which is acting as the contents of the JScrollPane, onto which the MouseListener should be attached.

The following example simply calculates the difference between the point at which the user clicked and the amount they have dragged. It then applies this delta to the JViewport's viewRect and uses JComponent#scrollRectToVisible to update the viewable area, simple :)

Sample Image

public class Test {

public static void main(String[] args) {
new Test();
}

public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}

JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}

public class TestPane extends JPanel {

private JLabel map;

public TestPane() {
setLayout(new BorderLayout());
try {
map = new JLabel(new ImageIcon(ImageIO.read(new File("c:/treasuremap.jpg"))));
map.setAutoscrolls(true);
add(new JScrollPane(map));

MouseAdapter ma = new MouseAdapter() {

private Point origin;

@Override
public void mousePressed(MouseEvent e) {
origin = new Point(e.getPoint());
}

@Override
public void mouseReleased(MouseEvent e) {
}

@Override
public void mouseDragged(MouseEvent e) {
if (origin != null) {
JViewport viewPort = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, map);
if (viewPort != null) {
int deltaX = origin.x - e.getX();
int deltaY = origin.y - e.getY();

Rectangle view = viewPort.getViewRect();
view.x += deltaX;
view.y += deltaY;

map.scrollRectToVisible(view);
}
}
}

};

map.addMouseListener(ma);
map.addMouseMotionListener(ma);
} catch (IOException ex) {
ex.printStackTrace();
}
}

@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}

}

}

Why JScrollPane does not react to mouse wheel events?

Walter beat me to analysing the issue :-)

Adding a bit of detail:

It's correct that a JScrollPane supports mouseWheelHandling. According to the rules of mouseEvent dispatching, the top-most (in z-order) component gets the event, and that's the scrollPane around the textArea. So if wheeling the textarea is not required, a simple solution might be to disable the wheel-support in its scrollPane. And JScrollPane even has api for doing it:

scrollPane.setWheelScrollingEnabled(false); 

Unfortunately, that doesn't work. Reason it's not working is that this property has no effect in the event dispatch chain which ultimately calls into eventTypeEnabled:

case MouseEvent.MOUSE_WHEEL:
if ((eventMask & AWTEvent.MOUSE_WHEEL_EVENT_MASK) != 0 ||
mouseWheelListener != null) {
return true;
}

This returns true if a mouseWheelListener is installed - which is done unconditionally by BasicScrollPaneUI, and not removed when the wheelEnabled property is changed (the ui doesn't even listen to that property ...) Plus the listener simply does nothing if the property is false. At least one of those facts is a bug, the ui should

  • either remove/add the listener depending on wheelEnabled
  • or: implement the listener such that it dispatches the event up the chain (as Walter does in his example)

The first option can be handled by application code:

scrollPane = new JScrollPane();
scrollPane.removeMouseWheelListener(scrollPane.getMouseWheelListeners()[0]);

it's a bit of a hack (as bug-workarounds always are :-), production code would have to listen to the wheelEnable to re-install if needed plus listen to LAF changes to update/re-remove the listeners installed by the ui.

Implementing the second option in slight modification (as to Walter's dispatching) by subclassing the JScrollPane and dispatch the event to parent if the wheelEnabled is false:

scrollPane = new JScrollPane() {

@Override
protected void processMouseWheelEvent(MouseWheelEvent e) {
if (!isWheelScrollingEnabled()) {
if (getParent() != null)
getParent().dispatchEvent(
SwingUtilities.convertMouseEvent(this, e, getParent()));
return;
}
super.processMouseWheelEvent(e);
}

};
scrollPane.setWheelScrollingEnabled(false);


Related Topics



Leave a reply



Submit