How to set the scrollbar to bottom after JTextPane finish loading image? - java

I'm making a chat room with a JTextPane with html function. Users are able to input html tag to show image on the screen. But I'm having a problem to keep the scrollbar at bottom. I already try to do
SwingUtilities.invokeLater(new Runnable() {
public void run() {
vertical.setValue(vertical.getMaximum());
}
});
but the scrollbar scroll down then scroll up again. It seems like the picture finish loading after the function been called. I also tried:
ClientScreen._chatMsgPane.setCaretPosition(_chatMsgPane.getDocument().getLength());
but the result are the same. Is there's any event will trigger after all image finish loading? Or is there any other way to fix this?

I've never tried this on a JTextPane that is loading an image but Smart Scrolling might work for you.

JComponent has a method named scrollRectToVisible which is designed to do this:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int end = chatMsgPane.getDocument().getLength();
try {
chatMsgPane.scrollRectToVisible(chatMsgPane.modelToView(end));
} catch (BadLocationException e) {
throw new RuntimeException(e); // Should never get here.
}
}
});
I'm not entirely sure how you are addng the image to the JTextPane, but if you are loading it yourself, you can load it in a different thread with ImageIO.read and then scroll to bottom when the read is finished; or, if you want to show the image progressively, you can obtain an ImageReader from ImageIO, and pass your own listener to its addIIOReadProgressListener method.

Related

Issue adding statements to JTextArea with delays in between

In the code below I am running my method which adds text to a JTextArea, then i wait 4 seconds and add more text. However it simply waits four seconds then puts all the text down at once. How can I make it so it adds the first text, waits then adds the second block of text?
public static void configuresettings() {
GUI.add("To Begin, Go to www.opionsxo.com");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
GUI.add("Welcome to Configure Settings!");
}
I figured it out, if anyone is interested in how. Then view the code below...
public static void configuresettings() {
GUI.add("To Begin, Go to www.opionsxo.com");
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
GUI.add("Welcome to Configure Settings!");
}
};
Timer timer = new Timer( 4000, actionListener );
timer.setRepeats(false);
timer.start();
}
You shouldn't use Thread.sleep with swing, or it will block your application. For instance, if you resize your window or move a window in front of your application, it won't repaint as needed, because you stopped it. Instead, you should be using Swing Timers.
Your problem is probably because you should revalidate() your JTextArea before sleep or something, but as said, you should change the implementation of it.
Read more here: http://docs.oracle.com/javase/tutorial/uiswing/misc/timer.html
Don't do anything within the context of the Event Dispatching Thread that might cause it to stop, like calling Thread.sleep.
Instead, trying using a javax.swing.Timer or SwingWorker
Take a look at:
Concurrency in Swing
Worker Threads and SwingWorker
How to Use Swing Timers
For more details
For example:
java for-loop in GUI TextArea
Displaying contents of String array in Swing component as iterations using Time delay. JAVA
I cant run JTextArea multiple times?

How to fix JTextArea Scroll to bottom?

I have already seen : How to set AUTO-SCROLLING of JTextArea in Java GUI?
blackArea = new JTextArea();
blackArea.setFont(new Font("Tahoma", Font.BOLD, 11));
blackArea.setText("Loggend on Administrator...\n" +date);
blackArea.setForeground(Color.RED);
blackArea.setBackground(Color.BLACK);
DefaultCaret caret = (DefaultCaret)blackArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
blackArea.setCaretPosition(blackArea.getDocument().getLength());
scrollPane.setViewportView(blackArea);
This works well. When update to JTextArea, the scroll moved to bottom automatically so I could see the refresh data. But the problem is, when I click the any space in JTextArea, the auto-scrolling is stopped. No more auto scroll works. How to fix it?
SUPPLEMENT : I added text to blackArea calling GUI.blackArea.append("bla bla bla"); GUI is class name where above code included. Thanks for #hovercraft-full-of-eels
Check out Smart Scrolling. It is an improvement over the other scrolling answer.
It the scrollpane is at the bottom when the append occurs it will continue to keep the scrollpane at the bottom. However, if the user has move the viewport from the bottom then the append will not automatically scroll to the bottom.
You don't show where you are adding or appending text to the JTextArea, and this is critical since the changing of the caret position should occur there.
Edit
You state:
Sorry, I just append text in other class, just calling GUI.blackArea.append("bla bla bla"); Should I use SwingUtilities.invokeLater?
I know you've got a decent answer from Rob Camick, a true Swing guru, but I also have to add that you really shouldn't expose your class's fields that way (and hopefully none of your components are declared static as your code suggests that they may be). Instead expose public methods that allow controlled ability to change the state of your fields. For instance your GUI class could have a public method like so
public void blackAreaAppend(String text) {
blackArea.append(text);
// code here to advance cursor
}
Or if this method is always called off of the EDT:
public void blackAreaAppend(String text) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
blackArea.append(text);
// code here to advance cursor
}
});
}
Or if you're just not sure:
public void blackAreaAppend(String text) {
if (SwingUtilities.isEventDispatchThread()) {
blackArea.append(text);
// code here to advance cursor
} else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
blackArea.append(text);
// code here to advance cursor
}
});
}
}
I solved this problem. this is the problem of view-point. when I click any space on JTextarea, the location of caret is changed, so view-point is changed too. Following my code, there is no update with view point.
So, I made a method :
public static void addLog(final String log){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
GUI.blackArea.append(log);
GUI.blackArea.setCaretPosition(GUI.blackArea.getDocument().getLength());
}
});
}
I changed blackArea.append("...") to `addLog("...). and I got out of this problem, However, remember that you can't fix caret positon while updating.

Button doesn't disable when function is called

I'm really lost with this:
In this Case the Button should diasable (btnStart.setEnable(false)) when it got clicked.
after this it should call a function, located in an other class.
Everything works, except that the btnStart doesn't disable on click but after the function ist called.
So this:
#Override
public void actionPerformed(ActionEvent buttonKLick) {
if(buttonKLick.getSource() == this.btnStart){
btnStart.setEnabled(false);
try {
Funktionen.fileFinder(Pfad); //The Function
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
Will call the function FileFinder and then disable the button although the button should be disabled before.
You've likely got a threading issue where your other method is taking time and locking up the Swing event thread, preventing Swing from properly painting the button as disabled. What happens if you use a background thread?
i.e.,
if(buttonKLick.getSource() == this.btnStart){
btnStart.setEnabled(false);
new Thread(new Runnable() {
public void run() {
try {
Funktionen.fileFinder(Pfad); //The Function
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}).start();
}
Edit:
Please be sure to read: Lesson: Concurrency in Swing.
No, it will disable the button right when you call setEnabled( false ). Just try calling isEnabled() before you call that function.
What you observe is that the button is only painted in disabled state after the function is executed. That is because your function occupies the one and only thread used to update the UI (the Event Dispatch Thread).
Solution: execute your function on a worker thread, for example using a SwingWorker. Also, read the Swing concurrency guide. This explains in more detail what I mentioned here

Doing Live/Dynamic changes in Swing

I am making a game having squares in it (a grid of panels) and when the game ends there is an algorithm that changes the color of the panels one by one in a "live" fashion where the user watches the squares change color slowly. I try something like:
Thread.sleep(1000);
grid.getComponent(boxNumber).setBackground(Color.YELLOW);
Thread.sleep(1000);
grid.getComponent(boxNumber).setBackground(Color.ORANGE);
Although the color of a box changes to Yellow, it does not change to Orange afterwards. Anyone have any ideas? Hope I was able to be clear.
Read the section from the Swing tutorial on Concurrency to understand why you should not be using the sleep() method.
One solution is to use a SwingWorker, then you can 'publish' the color of the component so it can be updated properly on the EDT and you can invoke the sleep() method in the worker as well.
These need to happen on the Swing event thread. call set background via:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
grid.getComponent(boxNumber).setBackground(Color.ORANGE);
}
});
Note, your Thread.sleep() should not be in the event thread (or directly from within a Swing event listener (ActionListener, WindowListener, etc).
It would also be prudent to look the Swing Timing Framework which is specifically for things like this.
-Generally its not a good idea to do Thread.sleep(1000); in the EDT. You should try using Timers.
-You also need to call revalidate()/validate() and repaint() afterward.
So maybe something like this:
Timer yellowTimer = new Timer(1000,new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
jtp.setBackground(Color.YELLOW);
//call revalidate()/validate() and repaint() afterward
jtp.revalidate();
jtp.repaint();
}
});
yellowTimer.setRepeats(false);
Timer orangeTimer = new Timer(2000,new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
jtp.setBackground(Color.ORANGE);
//call revalidate()/validate() and repaint() afterward
jtp.revalidate();
jtp.repaint();
}
});
orangeTimer.setRepeats(false);
yellowTimer.start();
orangeTimer.start();

List in JScrollPane painting outside the viewport

I have a list, each item of which has several things in it, including a JProgressBar which can be updated a lot. Each time one of the items updates its JProgressBar, the ListDataListener on the list tries to scroll it to the visible range using
/*
* This makes the updating content item automatically scroll
* into view if it is off the viewport.
*/
public void contentsChanged(final ListDataEvent evt) {
if (!EventQueue.isDispatchThread()) {
/**
* Make sure the scrolling happens in the graphics "dispatch" thread.
*/
EventQueue.invokeLater(new Runnable() {
public void run() {
contentsChanged(evt);
}
});
}
if (playbackInProgress) {
int index = evt.getIndex0();
currentContentList.ensureIndexIsVisible(index);
}
}
Note that I'm trying to make sure the scrolling is done in the dispatch thread, since I thought maybe the problem was it being scrolled while it was repainting. And yet, I still have a problem where if things are really active, some of the list items paint outside of the viewport, overwriting what's outside the JScrollPane. Forcing an exposure event will repaint those things, but it's annoying.
Is there anything else I need to look out for to stop these things painting outside of their clipping area?
Have you tried explicitly enabling double-buffering on the JList and/or the components that it is drawing over? (with:setDoubleBuffered(boolean aFlag))
Another thought is that you might need to exit the function immediately after delegating to the EDT. The way your code is written, it looks like the update will happen in both threads if ContentChanged is invoked from a non-EDT thread. Logging in the first if (or set a breakpoint in the if -- but not in the runnable -- should help determine if that is your problem.
eg:
public void contentsChanged(final ListDataEvent evt)
{
if (!EventQueue.isDispatchThread())
{
log.debug("Delegating contentsChanged(...) to EDT");
EventQueue.invokeLater(new Runnable()
{
public void run()
{
contentsChanged(evt);
}
});
// don't run ensureIndexIsVisible twice:
return;
}
if (playbackInProgress)
{
int index = evt.getIndex0();
currentContentList.ensureIndexIsVisible(index);
}
}

Categories

Resources