I want to show the Processing Icon on the button once it is clicked to make understand the user that application is loading the page, he has to wait until it is completed.
Following is the Wait Cursor I got for the below code:
The code I developed to meet my above requirement is as follows:
public static boolean waitCursorIsShowing;
public Cursor waitCursor, defaultCursor;
waitCursor = new Cursor(Cursor.WAIT_CURSOR);
defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR);
waitCursorIsShowing = true;
if (waitCursorIsShowing)
{
JFrame frame.setCursor(waitCursor);
-----------------------------------------------
/* code to load the page/ perform the business logic */
-----------------------------------------------
waitCursorIsShowing = false;
frame.setCursor(defaultCursor);
}
This code is working well according to my requirement. But when I want to show the other Processing Image Icon on the button when it is clicked, the result I am getting is in reverse order. I mean when the button is clicked and page is loading, no Processing Image Icon is shown on the button, but when action is performed and button is released after Page load, then the application is showing the Processing Image Icon on the button.
Following is the Processing Image Icon I am getting for the below code:
Please help me where I am coding incorrect. Following is the modified code.
public static boolean waitCursorIsShowing;
public Cursor waitCursor, defaultCursor;
waitCursor = new Cursor(Cursor.WAIT_CURSOR);
defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR);
waitCursorIsShowing = true;
if (waitCursorIsShowing)
{
URL url = getClass().getResource("/Images/loader8.gif");
b4.setText("Loading");
b4.setIcon(new ImageIcon(url));
-----------------------------------------------
/* code to load the page/ perform the business logic */
-----------------------------------------------
waitCursorIsShowing = false;
b4.setIcon(new ImageIcon());
b4.setText("Submit");
}
b4 is the button on which I want to get the Processing Image Icon.
How to modify my code to customize the Icon on the button when it is clicked and application is loading the Page?
Remember that events are all processed on the event dispatch thread, which is the same thread that handles painting. If you do your page loading on that thread, then the frame will not be able to repaint until the loading is complete. Instead, do the loading on a background thread, and use a SwingUtilities.invokeLater() call to set the icon back when you are done.
You can use SwingWorker:
final JFrame frame = new JFrame("Example");
final JButton button = new JButton("Please, press me!");
frame.getContentPane().add(button, BorderLayout.NORTH);
final JTextPane pane = new JTextPane();
frame.getContentPane().add(pane);
final Cursor defaultCursor = Cursor.getDefaultCursor();
final Cursor busyCursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
final ImageIcon loadingIcon = new ImageIcon(ImageIO.read(getClass().getResource("/Images/loader8.gif")));
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
button.setIcon(loadingIcon);
frame.setCursor(busyCursor);
SwingWorker<String, Object> worker = new SwingWorker<String, Object>() {
#Override
public String doInBackground() throws InterruptedException {
// do some work...
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 25; i++) {
sb.append((char)(i + 65));
Thread.sleep(25);
}
return sb.toString();
}
#Override
protected void done() {
try {
//set result of doInBackground work
pane.setText(get());
frame.setCursor(defaultCursor);
button.setIcon(null);
} catch (Exception ignore) {
}
}
};
worker.execute();
}
});
frame.setSize(new Dimension(400, 300));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
Related
I have a java program that load a text file as input, read its content, modify some strings and then prints the result to a textarea. Due to several seconds required by this operation i would like to show a JProgressBar during this activity in order to inform the user that the execution is in progress and when the activity is completed close the dialog containing the JprogressBar and print the results.
Here is the code:
JButton btnCaricaFile = new JButton("Load text file");
panel.add(btnCaricaFile);
btnCaricaFile.setIcon(UIManager.getIcon("FileView.directoryIcon"));
btnCaricaFile.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
//JFileChooser choice = null;
final JFileChooser choice = new JFileChooser(userDir +"/Desktop");
int option = choice.showOpenDialog(GUI.this);
if (option == JFileChooser.APPROVE_OPTION) {
final JDialog dialog = new JDialog(GUI.this, "In progress", true);
JProgressBar progressBar = new JProgressBar(0, 100);
progressBar.setIndeterminate(true);
dialog.getContentPane().add(BorderLayout.CENTER, progressBar);
dialog.getContentPane().add(BorderLayout.NORTH, new JLabel("Elaborating strings..."));
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.setSize(300, 75);
dialog.setLocationRelativeTo(GUI.this);
Thread t = new Thread(new Runnable() {
public void run() {
dialog.setVisible(true);
File file = choice.getSelectedFile();
lista.clear();
textArea.setText("");
lista = loadFile.openFile(file);
for(int i=0; i<lista.size(); i++) {
textArea.append(lista.get(i)+"\n");
}
dialog.setVisible(false);
}
});
t.start();
}
}
});
For this purpose i'm using a JDialog as container for the JProgressBar executed by an appropriate thread. The problem is that the progress bar is shown for an infinite time and is not printed anything to the textarea.
Could you help me to solve this?
Thanks
Yes, you're creating a background thread for your file reading, good, but you're also making Swing calls from within this same background thread, not good, and this is likely tying up the Swing event thread inappropriately. The key is to keep your threading separate -- background work goes in the background thread, and Swing work goes only in the Swing thread. Please read Lesson: Concurrency in Swing fore more on this.
Myself, I would create and use a SwingWorker<Void, String>, and use the worker's publish/process method pair to send Strings to the JTextArea safely.
For example, something like...
final JDialog dialog = new JDialog(GUI.this, "In progress", true);
JProgressBar progressBar = new JProgressBar(0, 100);
progressBar.setIndeterminate(true);
dialog.getContentPane().add(BorderLayout.CENTER, progressBar);
dialog.getContentPane().add(BorderLayout.NORTH, new JLabel("Elaborating strings..."));
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.setSize(300, 75);
dialog.setLocationRelativeTo(GUI.this);
lista.clear();
SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
#Override
public Void doInBackground() throws Exception {
// all called *off* the event thread
lista = loadFile.openFile(file);
for (int i = 0; i < lista.size(); i++) {
publish(lista.get(i));
}
return null;
}
#Override
protected void process(List<String> chunks) {
// called on the event thread
for (String chunk : chunks) {
textArea.append(chunk + "\n");
}
}
// called on the event thread
public void done() {
dialog.setVisible(false);
// should call get() here to catch and handle
// any exceptions that the worker might have thrown
}
};
worker.execute();
dialog.setVisible(true); // call this last since dialog is modal
Note: code not tested nor compiled
I am trying to get my application to display a simple loading dialog so users know when a time intensive process is working and when its done. I just want it to show a simple "loading" using a gif I downloaded. I already tried using only text and it still doesn't work.
I can get the dialog to display (and disappear) when I want it to, the problem is nothing will display on the dialog (or frame) after displaying it. I have tried many different techniques and all give the same result, a blank dialog.
I finally made a separate class to display the dialog (with loading gif) and I got it to display properly (by itself), but when I run it from my main application, it shows a black dialog again. I tested putting the gif into a JOptionPane and it works, the problem with that is I can't close it at will.
Here is my custom code.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import java.util.logging.*;
import org.w3c.dom.*;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import javax.swing.filechooser.FileNameExtensionFilter;
public class Loader implements Runnable {
final JFileChooser jfc = new JFileChooser();
static JFrame frame = new JFrame();
Frame parentUI = new Frame();
JDialog dialog = new JDialog();
JLabel lbl_filename = new JLabel();
JLabel lbl_path = new JLabel();
static Loader load = new Loader(null);
public static void main(String[] args) throws InterruptedException, InvocationTargetException {
load.run();
frame.setVisible(true);
}
public Loader(Frame parent) {
init();
parentUI = parent;
}
#Override
public void run() {
createDialog(parentUI);
}
public final void init() {
JButton btn = new JButton("Open");
frame.setTitle("Loader Test");
frame.setSize(500, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setLayout(new FlowLayout());
btn.addActionListener(new Action1());
frame.add(btn);
frame.add(lbl_filename);
frame.add(lbl_path);
}
class Action1 implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
openFile();
load.Close();
}
}
private void createDialog(final Frame parent) {
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setTitle("Loader");
URL url = this.getClass().getResource("/resource/loader.gif");
Icon icon = new ImageIcon(url);
JLabel label = new JLabel(icon);
dialog.add(label);
dialog.pack();
dialog.setLocationRelativeTo(parent);
}
public void Show(Boolean visible) {
this.run();
dialog.setVisible(visible);
}
public void Close() {
dialog.setVisible(false);
}
private void setJFCFilter(String file, String ext) {
FileNameExtensionFilter filter = new FileNameExtensionFilter(file, ext);
jfc.setFileFilter(filter);
}
private void openFile() {
File default_dir = new File(".");
jfc.setCurrentDirectory(default_dir);
setJFCFilter("Scalable Vector Graphics", "svg");
int returnVal = jfc.showOpenDialog(parentUI);
if (returnVal == JFileChooser.APPROVE_OPTION) {
String path = jfc.getSelectedFile().getAbsolutePath();
String fileName = jfc.getSelectedFile().getName();
lbl_filename.setText(fileName);
lbl_path.setText(path);
load.Show(true);
createDoc(path);
load.Close();
}
}
private void createDoc(String file) {
try {
NodeList svgIDPaths;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(file);
String xpathIDExp = "//g/#id";
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
XPathExpression expression = xpath.compile(xpathIDExp);
svgIDPaths = (NodeList)expression.evaluate(doc, XPathConstants.NODESET);
} catch (Exception ex) {
Logger.getLogger(Loader.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Edit: Use this file for testing -> svg_test.svg
I have tried calling it like this:
loader.show(true);
And also in its own thread like this:
private void load(final Boolean visible) {
Thread t = new Thread(new Runnable() {
#Override
public void run() {
loader.show(visible);
}
});
t.start();
}
Neither method works and gives me the same result, a blank dialog. I have had this issue in the past, but just gave up and removed it (loading dialog). I have tried it with a progress bar and simple text, nothing seems to work.
Also I tried it in a JOptionPane and it worked, but that's not desirable (I want to close/open when I want not via a button click).
private void load() {
ImageIcon icon = new ImageIcon(MainForm.class.getResource("/resource/loader.gif").getFile());
JOptionPane.showMessageDialog(null, "Loading...", "Loader", JOptionPane.INFORMATION_MESSAGE, icon);
}
I am aware you can't run multiple dialogs on the EDT and have to use a separate thread, but I'm using a separate thread and its not working (it works by itself).
(Also note I have one main application (frame) that is running/opening this second dialog).
Any assistance is appreciated.
You look to have a Swing threading issue where you have long-running code on the event thread messing up drawing of images, and my guess is that the long running code is in your createDoc method. Consider calling that from a background thread, such as from a SwingWorker, and calling close on your load object only after the worker has completed its work. For example something like so:
class Action1 implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
openFile();
// load.Close(); // get rid of this
}
}
// .......
private void openFile() {
// ....
load.Show(true); // load dialog on event thread
new SwingWorker<Void, Void>() {
protected Void doInBackground() throws Exception {
createDoc(path); // call this from background thread
return null;
};
protected void done() {
load.Close(); // only call this once createDoc has completed
// probably should call get() in here to catch all exceptions
};
}.execute();
}
I have written the following code to generate please wait JDialog while generation of decision tree but it opens up and appears to be blank
public JDialog pleasewait()
{
JDialog dialog = new JDialog();
JLabel label = new JLabel("Please wait...");
label.setIcon(new javax.swing.ImageIcon(getClass().getResource("/decision_tree_runner/load.gif"))); // NOI18N
dialog.setBackground(Color.BLACK);
dialog.setLocationRelativeTo(null);
dialog.setTitle("Please Wait...");
dialog.add(label);
dialog.pack();
return dialog;
}
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
JDialog dialog = pleasewait();
dialog.repaint();
dialog.setVisible(true);
FypProject fyp_project = new FypProject();
try {fyp_project.main_fypproject();} catch (SQLException ex) {}
dialog.setVisible(false);
}
It is likely that fyp_project.main_fypproject() is a long running/blocking call, which when called from within the context of the Event Dispatching Thread, will stop it from been able to process new events, including the repaint request.
Consider using something like a SwingWorker, opening the dialog first, execute the worker and when it's done method is called, close the dialog
Take a look at Concurrency in Swing and Worker Threads and SwingWorker for more details
I think it has to do with where in the dialog the JLabel gets added. The lack of our layout manager makes this difficult.
Try adding this before you add the JLabel:
dialog.setLayout(new GridLayout());
and remove:
dialog.pack();
i did the following steps
created separate form for pleasewait
created thread for fyp_project.main_fypproject()
passed object of form to thread class
in run method setvisible() option of pleasewait to false
public class thread_for_pleasewait implements Runnable{
Thread t ;
please_wait_form pwf;
decision_tree dt;
FypProject fyp_project = new FypProject();
#Override
public void run()
{
String[] args = null;
try {
fyp_project.main_fypproject();
pwf.setVisible(false);
dt.setVisible(true);
} catch (SQLException ex) {
Logger.getLogger(thread_for_pleasewait.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void start(please_wait_form pwf,decision_tree dt)
{
t = new Thread(this);
t.start();
this.pwf=pwf;
this.dt=dt;
}
}
There is a JTable in my application with resizable header columns. Normally when I move the cursor over table header for resizing, the cursor icon changes to resize arrow, like <-->.
But things are different in the following scenario.
There is a button action in the same Frame, and during action performed, I am setting the cursor to busy icon and change it back to default cursor once the action is completed, using Container.setCursor(Cursor cursor) method.
Sometimes if I move the cursor over table header of resizing, after a button action, the cursor icon does not change to resize arrow, cursor does not change at all.
Can this be considered as a bug in Java Swing or is there a solution for this issue?
Update : Sample code attached
import java.util.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class ColumnResizeIconTest extends JFrame {
JScrollPane scrollPane;
JTable table;
JButton button;
public ColumnResizeIconTest() {
setLayout(new BorderLayout());
addComponents();
setSize(300,300);
}
private void addComponents() {
addButton();
addTable();
}
private void addButton() {
button = new JButton("Click Me");
button.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
setWaitCursor();
for(int i=0; i<2000; i++) {
System.out.print(i);
}
setDefaultCursor();
}
});
add(button, BorderLayout.NORTH);
}
private void addTable() {
scrollPane = new JScrollPane(createTable());
add(scrollPane, BorderLayout.CENTER);
}
private JTable createTable() {
Object[][] cellData = { { "1-1", "1-2","1-3" }, { "2-1", "2-2", "2-3" }, { "3-1", "3-2", "3-3" } };
String[] columnNames = { "column1", "column2", "column3" };
table = new JTable(cellData, columnNames);
return table;
}
private void setWaitCursor() {
Container container = getContentPane();
setWaitCursor(container);
}
private void setWaitCursor(Container container) {
for(int iCount = 0; iCount < container.getComponentCount(); iCount++) {
Component child = (Component) container.getComponent(iCount);
if(child instanceof Container) {
setWaitCursor((Container) child);
} else {
child.setCursor(new Cursor(Cursor.WAIT_CURSOR));
}
}
container.setCursor(new Cursor(Cursor.WAIT_CURSOR));
}
private void setDefaultCursor() {
Container container = getContentPane();
setDefaultCursor(container);
}
private void setDefaultCursor(Container container) {
for(int iCount = 0; iCount < container.getComponentCount(); iCount++) {
Component child = (Component) container.getComponent(iCount);
if(child instanceof Container) {
setDefaultCursor((Container) child);
} else {
child.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
}
container.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
public static void main(String[] argv) throws Exception {
ColumnResizeIconTest test = new ColumnResizeIconTest();
test.setVisible(true);
}
}
Click on the button a few times and try to resize the table column. The cursor is stuck with Default cursor.
As already mentioned in my comment: it's not entirely trivial to re-/set the cursors, not even for a single component :-) The base problem (in the recursive cursor setting to wait) is the assumption that all components do have the default cursor.
As you see on the table header, that assumption is not correct: on that component, the "default" is either the defaultCursor or the resizeCursor, depending on mouse location. Additionally, the internal cursor toggling is not very intelligent: it doesn't check for state (from the top of my head, was hit by that fact a while ago :-)
Not entirely sure what you want to reach, so don't have a concrete solution, except dropping the recursive setting entirely, it's too hard to get right. Options might be
make the glassPane (of the frame's rootpane) visible and set the waitCursor on it
use JLayer (jdk7) or JXLayer (jdk6) on a smaller area and set the waitCursor on that
use a less intrusive visualization, f.i. JProgressBar or a JXBusyLabel (in the SwingX project) somewhere
Addendum (for #mKorbel :-)
the problem is easily reproducible, with a little change to the OPs SSCCE (thanks for that!): change the addButton method as below, then click on the button and while the wait cursor is shown, move the mouse into the header and then to another column (across the column border). Doing so several times will lead to unpredicable cursors on the header ...
private void addButton() {
button = new JButton("Click Me");
final ActionListener off = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setDefaultCursor();
button.setEnabled(true);
}
};
button.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
setWaitCursor();
button.setEnabled(false);
Timer timer = new Timer(2000, off);
timer.setRepeats(false);
timer.start();
}
});
add(button, BorderLayout.NORTH);
}
1) you have redirect code
for(int i=0; i<2000; i++) {
System.out.print(i);
}
to the BackGround Task, you can implements javax.swing.Timer, SwingWorker, or wrap these code lines inside Runnable#Thread, example here
2) you have to restore Cursor on Error/Exception too, that's probably reason why Cursor wasn't changed
How to make JTextPane autoscroll only when scroll bar is at bottom and scroll lock is off? This shouldn't have anything to do with caret, which is what I seem to be finding all over Google. :(
I think my program below meets your requirements exactly, with one possible caveat: you're not allowed to type in the text area. So this would be good for a log viewer, but not an interactive console. The code runs a little long because I have made it into a ready-to-run demo of the approach. I suggest running the program as-is and checking out the behavior. If the behavior works well for you, then invest a little time in studying the code. I have included comments in the code to highlight some of the more important sections.
Update 2013-07-17: You may also want to check out random dude's solution in his separate answer farther down the page. His approach is more elegant than mine.
Also see Swing: Scroll to bottom of JScrollPane, conditional on current viewport location for a potential solution that does not interfere with the caret position.
SCCE source code follows:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.Timer;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
public class ScrollingJTextAreaExample extends JFrame {
// Worker thread to help periodically append example messages to JTextArea
Timer timer = new Timer();
// Merely informative counter, will be displayed with the example messages
int messageCounter = 0;
// GUI components
JScrollPane jScrollPane;
JTextArea jTextArea;
public ScrollingJTextAreaExample() {
initComponents(); // Boiler plate GUI construction and layout
// Configure JTextArea to not update the cursor position after
// inserting or appending text to the JTextArea. This disables the
// JTextArea's usual behavior of scrolling automatically whenever
// inserting or appending text into the JTextArea: we want scrolling
// to only occur at our discretion, not blindly. NOTE that this
// breaks normal typing into the JTextArea. This approach assumes
// that all updates to the ScrollingJTextArea are programmatic.
DefaultCaret caret = (DefaultCaret) jTextArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
// Schedule a task to periodically append example messages to jTextArea
timer.schedule(new TextGeneratorTask(), 250, 250);
// This DocumentListener takes care of re-scrolling when appropriate
Document document = jTextArea.getDocument();
document.addDocumentListener(new ScrollingDocumentListener());
}
// Boring, vanilla GUI construction and layout code
private void initComponents() {
jScrollPane = new javax.swing.JScrollPane();
jTextArea = new javax.swing.JTextArea();
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
jScrollPane.setViewportView(jTextArea);
getContentPane().add(jScrollPane, java.awt.BorderLayout.CENTER);
setSize(320, 240);
setLocationRelativeTo(null);
}
// ScrollingDocumentListener takes care of re-scrolling when appropriate
class ScrollingDocumentListener implements DocumentListener {
public void changedUpdate(DocumentEvent e) {
maybeScrollToBottom();
}
public void insertUpdate(DocumentEvent e) {
maybeScrollToBottom();
}
public void removeUpdate(DocumentEvent e) {
maybeScrollToBottom();
}
private void maybeScrollToBottom() {
JScrollBar scrollBar = jScrollPane.getVerticalScrollBar();
boolean scrollBarAtBottom = isScrollBarFullyExtended(scrollBar);
boolean scrollLock = Toolkit.getDefaultToolkit()
.getLockingKeyState(KeyEvent.VK_SCROLL_LOCK);
if (scrollBarAtBottom && !scrollLock) {
// Push the call to "scrollToBottom" back TWO PLACES on the
// AWT-EDT queue so that it runs *after* Swing has had an
// opportunity to "react" to the appending of new text:
// this ensures that we "scrollToBottom" only after a new
// bottom has been recalculated during the natural
// revalidation of the GUI that occurs after having
// appending new text to the JTextArea.
EventQueue.invokeLater(new Runnable() {
public void run() {
EventQueue.invokeLater(new Runnable() {
public void run() {
scrollToBottom(jTextArea);
}
});
}
});
}
}
}
class TextGeneratorTask extends TimerTask {
public void run() {
EventQueue.invokeLater(new Runnable() {
public void run() {
String message = (++messageCounter)
+ " Lorem ipsum dolor sit amet, consectetur"
+ " adipisicing elit, sed do eiusmod tempor"
+ " incididunt ut labore et dolore magna aliqua.\n";
jTextArea.append(message);
}
});
}
}
public static boolean isScrollBarFullyExtended(JScrollBar vScrollBar) {
BoundedRangeModel model = vScrollBar.getModel();
return (model.getExtent() + model.getValue()) == model.getMaximum();
}
public static void scrollToBottom(JComponent component) {
Rectangle visibleRect = component.getVisibleRect();
visibleRect.y = component.getHeight() - visibleRect.height;
component.scrollRectToVisible(visibleRect);
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new ScrollingJTextAreaExample().setVisible(true);
}
});
}
}
Little late to this question, but I came up with this solution.
conversationPane = new JTextPane();
final JScrollPane conversationScrollPane = new JScrollPane(conversationPane);
conversationScrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
BoundedRangeModel brm = conversationScrollPane.getVerticalScrollBar().getModel();
boolean wasAtBottom = true;
public void adjustmentValueChanged(AdjustmentEvent e) {
if (!brm.getValueIsAdjusting()) {
if (wasAtBottom)
brm.setValue(brm.getMaximum());
} else
wasAtBottom = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
}
});
Seems to work perfectly for my needs. Little explanation: Essentially if the scroll bar is not being moved by a person and the bar was last at the maximum/bottom then reset it to the maximum. If it's being manually adjusted, then check to see if it was adjusted to be at the bottom.
Text Area Scrolling may be of interest.
I have no idea how the scroll lock key affects it. I found the following from the Wikipedia page on Scroll Lock:
Therefore, Scroll Lock can be regarded as a defunct feature in almost all modern programs and operating systems.
So I wouldn't worry about it.
I needed to do the same for a logging text area. The solutions I found on the web did not worked for me (they either stop auto scrolling when logging to much messages quickly, or they blocked the scrollbar at bottom even if you scroll up whith your mouse wheel).
I did it this way :
public static void makeTextAreaAutoScroll(JTextArea textArea) {
// Get the text area's scroll pane :
final JScrollPane scrollPane = (JScrollPane) (textArea.getParent().getParent());
// Disable the auto scroll :
((DefaultCaret)textArea.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
// Add a listener to the vertical scroll bar :
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
private int _val = 0;
private int _ext = 0;
private int _max = 0;
private final BoundedRangeModel _model = scrollPane.getVerticalScrollBar().getModel();
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// Get the new max :
int newMax = _model.getMaximum();
// If the new max has changed and if we were scrolled to bottom :
if (newMax != _max && (_val + _ext == _max) ) {
// Scroll to bottom :
_model.setValue(_model.getMaximum() - _model.getExtent());
}
// Save the new values :
_val = _model.getValue();
_ext = _model.getExtent();
_max = _model.getMaximum();
}
});
}
Just use it this way :
makeTextAreaAutoScroll(yourTextArea);
You can test with this piece of code :
new Timer().schedule(new TimerTask() {
#Override
public void run() {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
String line = "test " + Math.random();
yourTextArea.append(yourTextArea.getText().isEmpty() ? line : "\n" + line);
}
});
}
}, 0, 5);
Now your text area should auto scroll if the scroll bar is at bottom, stop auto scrolling if you move the scroll bar (by dragging the bar or by using the wheel), and auto scroll again if you put the scroll bar at bottom again.
Try this :
JTextArea txt = new JTextArea();
JScrollPane jsp = new JScrollPane(history, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
txt.setCaretPosition(txt.getDocument().getLength()); // do this afeter any event
Hope that helps you
After I read Mike Clark and random dude's solution, I end up with below snippet code.
private boolean doAutoScroll = true;
private JTextPane textPane;
private JScrollPane scrollPane;
public void setup() {
/* Left Panel */
textPane = new JTextPane();
textPane.setPreferredSize(new Dimension(600, 400)); // width, height
/*
* Not update the cursor position after inserting or appending text to the JTextPane.
* [NOTE]
* This breaks normal typing into the JTextPane.
* This approach assumes that all updates to the JTextPane are programmatic.
*/
DefaultCaret caret = (DefaultCaret) textPane.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
scrollPane = new JScrollPane(textPane);
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
BoundedRangeModel brm = scrollPane.getVerticalScrollBar().getModel();
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// Invoked when user select and move the cursor of scroll by mouse explicitly.
if (!brm.getValueIsAdjusting()) {
if (doAutoScroll) brm.setValue(brm. getMaximum());
} else {
// doAutoScroll will be set to true when user reaches at the bottom of document.
doAutoScroll = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
}
}
});
scrollPane.addMouseWheelListener(new MouseWheelListener() {
BoundedRangeModel brm = scrollPane.getVerticalScrollBar().getModel();
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
// Invoked when user use mouse wheel to scroll
if (e.getWheelRotation() < 0) {
// If user trying to scroll up, doAutoScroll should be false.
doAutoScroll = false;
} else {
// doAutoScroll will be set to true when user reaches at the bottom of document.
doAutoScroll = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
}
}
});
}
The difference is that it is additionally using MouseWheelListener to update doAutoScroll flag even if user uses mouse wheel to scroll up and down.