Cursor icon does not change after triggering setCursor method - java

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

Related

Adding a timer to KeyListener

I extended a JFrame class and have my own model and JPanel extended classes as instance variables. I implemented KeyListener to my JFrame and it works with the arrow keys but my model moves extremely slow around the frame when I hold the keys down. My question is how do I attach the KeyListener methods to a timer or do something to make my model move faster when I hold the keys. Also if it is possible, how can the model move two directions at once, say left and up?
public class GameController extends JFrame implements KeyListener,ActionListener
{
private GamePieces p;
private GamePanel panel;
private Timer timer;
public GameController()
{
super("Balls");
setSize(800, 600);
timer = new Timer(10, this);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c = getContentPane();
c.setLayout(new BorderLayout());
p = new GamePieces();
panel = new GamePanel();
p.addObserver(panel);
c.add(panel);
addKeyListener(this);
panel.update(p, null);
setResizable(false);
timer.start();
}
public void actionPerformed(ActionEvent e)
{
String actionCommand = e.getActionCommand();
if (e.getSource() == timer)
{
p.checkEat();
p.moveOthers();
panel.update(p, null);
}
}
public void keyTyped(KeyEvent e)
{
int s = 0;
}
public void keyPressed(KeyEvent e)
{
int key = e.getKeyCode();
if (key==38)
{
p.up();
panel.update(p, null);
}
else if (key==40)
{
p.down();
panel.update(p, null);
}
else if (key==39)
{
p.right();
panel.update(p, null);
}
else if (key==37)
{
p.left();
panel.update(p, null);
}
}
public void keyReleased(KeyEvent e)
{
int o = 0;
}
public static void main(String[]args)
{
GameController a = new GameController();
a.setVisible(true);
}
}
You are on right way but it should be a little more sophisticated. Follow MVC pattern. You should have model which remembers where you panel should be - up(), down() methods only update this model. You should have viewer which shows panel at current position. panel.update() belongs to viewer. And then you should have controller which changes position (invoke these up and down, left and right) and invokes viewer when necessary - to show movement or just next frame.
Your KeyListener tells controller to move position in some intervals when key is pressed. And then it tells controller to stop doing so when key is released. See, all parts move smoothly and independently. Keylistener tells controller what to do, controller does - changes model and invokes viewer. Model does nothing - only holds data. Viewer does nothing only shows current data.
P.S. Don't forget thread safety since you will press multiple keys and they will invoke controller multiple times in the same thread.

How do I return a swing event if it not used?

The texts are inside JPanels and they are all inside a JScrollPane. I have set to catch the MouseWheelEvent (scrolling) so when the Ctrl key is pressed, and mouse wheel is turned, the texts would zoom in/out (font size is increased/decreased). This has lead to not being able to scroll through the list of fonts anymore unless I point the mouse cursor directly on the scrollbar. I want to know if there is a way to return the event when the Ctrl key is not held down. Is that possible? I looked through the MouseWheelEvent api and didn't find anything useful. Googling didn't give me anything either.
If I did understood you correctly the answer you are looking for should be like this the main idea to create to different mouse wheel event for your form and your scrollpane. Watch the entire animation you will see your problem is fixed , you can make your manipulations based on this idea
you should add below to your fields ;
public int defaultTextSize = 12;
boolean ctrlcheck =false;
this is jLabel4 to display CTRL is not pressed in constructor
jLabel4.setText("CTRL IS NOT PRESSED");
your scrollpane mouse event should be like this
private void jScrollPane1MouseWheelMoved(java.awt.event.MouseWheelEvent evt) {
int notches = evt.getWheelRotation();
if (notches < 0) {
if (ctrlcheck == true) {
defaultTextSize = defaultTextSize + 3;
Font zoomfont = new Font("Monospaced", Font.PLAIN, defaultTextSize);
jLabel1.setFont(zoomfont);
jLabel2.setFont(zoomfont);
jLabel3.setFont(zoomfont);
jLabel1.repaint();
jLabel2.repaint();
jLabel3.repaint();
} else {
System.out.println("CTRL IS NOT PRESSED");
}
} else {
if (ctrlcheck == true) {
defaultTextSize = defaultTextSize - 3;
Font zoomfont2 = new Font("Monospaced", Font.ITALIC, defaultTextSize);
jLabel1.setFont(zoomfont2);
jLabel2.setFont(zoomfont2);
jLabel3.setFont(zoomfont2);
jLabel1.repaint();
jLabel2.repaint();
jLabel3.repaint();
}
}
}
You need FORM Key Released Event Like this
private void formKeyReleased(java.awt.event.KeyEvent evt) {
jLabel4.setText("CTRL IS NOT PRESSED");
ctrlcheck = false;
}
You need separate event for formMouseWheelMoved
private void formMouseWheelMoved(java.awt.event.MouseWheelEvent evt) {
int notches = evt.getWheelRotation();
if (notches < 0) {
if (ctrlcheck == true) {
defaultTextSize = defaultTextSize + 3;
Font zoomfont = new Font("Monospaced", Font.PLAIN, defaultTextSize);
jLabel1.setFont(zoomfont);
jLabel2.setFont(zoomfont);
jLabel3.setFont(zoomfont);
jLabel1.repaint();
jLabel2.repaint();
jLabel3.repaint();
} else {
System.out.println("CTRL IS NOT PRESSED");
}
} else {
if (ctrlcheck == true) {
defaultTextSize = defaultTextSize - 3;
Font zoomfont2 = new Font("Monospaced", Font.ITALIC, defaultTextSize);
jLabel1.setFont(zoomfont2);
jLabel2.setFont(zoomfont2);
jLabel3.setFont(zoomfont2);
jLabel1.repaint();
jLabel2.repaint();
jLabel3.repaint();
}
}
}
You need Form Keypressed Event as well to check key is still pressed
private void formKeyPressed(java.awt.event.KeyEvent evt) {
int key = evt.getKeyCode();
if (key == KeyEvent.VK_CONTROL) {
jLabel4.setText("CTRL IS PRESSED");
ctrlcheck = true;
}
}
I used #MadProgrammer 's comment and got some idea from #okrman 's answer and came up with a better way of doing this. In my solution, I don't need to add two separate event listeners, namely, one to the JLabel and the other one to the JScrollPane. I use only one event listener on the JLabel. The main key here is Component#dispatchEvent as mentioned by #MadProgrammer. I just found (rather remembered from my foggy memory) an excellent way to get a hold of the parent JScrollPane inside JLabel.
Of course, the example I have given here is just an example. I have not set the widths or heights for any of the components. The soul purpose of this example is just to show how the problem is solved.
public class ParentScrollPane extends JScrollPane {
public ParentScrollPane() {
JPanel textContainer = new JPanel();
// I have multiple labels in a for loop, this is
// just an example
JLabel sLabel = new JLabel("SOME LABEL TEXT");
sLabel.setParent(thisPane);
textContainer.add(sLabel);
setViewportView(textContainer);
}
// this interface is important and key to the solution
public interface ParentSetter {
public void setParent(Component p);
}
private JScrollPane thisPane;
}
public class CustomLabel extends JLabel implements ParentScrollPane.ParentSetter {
public CustomLabel(String text) {
super(text);
// one event listener is suffice
addMouseWheelListener(new MouseWheelListener() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
// if the Ctrl key is down
if (e.isControlDown()) {
// then zoom the font in the JLabel
} else {
// then scroll the parent scroll pane
labelParent.dispatchEvent(e);
}
}
});
}
// pay very close attention to this and
// how the parent scroll pane is set here
#Override
public void setParent(Component lParent) {
labelParent = lParent;
}
private Component labelParent;
}

Best way of Changing the background of JButtons

Right now i change the background color of a button by using
button.setBackground(Color.WHITE);
That being an example.
But when i have a massive grid out of jbuttons (1000+), just running a for loop to change every buttons background is very, very slow. You can see the grid slowly turning white, box by box. I really don't want this
Is there a better way of changing every JButton on the grid to the same color at the same time?
This is how i am making the grid, the numbers used are only for example...
grid = new JPanel(new GridLayout(64, 64, 0, 0));
That's 4096 buttons, takes about 30+ seconds to change every button to the same color.
Edit 1: I need the buttons to be clickable, like when i click a button it turns blue for example. when all of the buttons are clicked, change the color of every button to white. Right now i have that working fine, but it is just slow to change the color of every button.
Edit 2: this is how i am changing the buttons:
new javax.swing.Timer(300, new ActionListener() {
int counter = 0;
public void actionPerformed(ActionEvent e) {
if (counter >= counterMax) {
((Timer) e.getSource()).stop();
}
Color bckgrndColor = (counter % 2 == 0) ? flashColor : Color.white;
for (JButton button : gridButton) {
button.setBackground(bckgrndColor);
}
counter++;
}
}).start();
The fact that you see the boxes being repainted individually indicates that either double buffering is turned off, or that the paint code in the button UI makes use of paintImmediately().
I tested your setup with 64x64 JButtons, an made sure that all UI operations were executed in the EDT (Event Dispatch Thread). I can confirm the effect you saw, changing the background of all buttons took about 1200 ms, with every box repainted immediately.
You can bypass the immediate repaints by setting the grid to non-visible before, and to visible after you changed the backgrounds:
grid.setVisible(false);
for (Component comp : grid.getComponents()) {
comp.setBackground(color);
}
grid.setVisible(true);
This caused the grid to do only one repaint, and reduced the time to ~300ms (factor 4).
This is still too slow for frequent updates, so you're better off with a custom component which draws the grid, or a flyweight container (what trashgod suggested in the comment to your question) if you want allow the grid cells to be arbitrary components.
You can get a considerable benefit if only visible buttons need to be repainted. In the MVC approach shown below, each button listens to a model that defines it's current state. Updating the model is quite fast compared to repainting. Although startup takes a few seconds, I see updates taking < 10 ms. in the steady-state. It's not as scalable as the flyweight pattern used by JTable, illustrated here, but it may serve.
import java.awt.*;
import java.awt.event.*;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;
import javax.swing.*;
/** #see https://stackoverflow.com/questions/6117908 */
public class UpdateTest {
private static final int ROW = 64;
private static final int COL = 64;
private static final int MAX = COL * ROW;
private final DataModel model = new DataModel(MAX);
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new UpdateTest().create();
}
});
}
void create() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(ROW, COL));
for (int i = 0; i < MAX; i++) {
panel.add(new ViewPanel(model, i));
}
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long start = System.nanoTime();
model.update();
System.out.println(
(System.nanoTime() - start) / (1000 * 1000));
}
});
JFrame f = new JFrame("JTextTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(panel), BorderLayout.CENTER);
f.setPreferredSize(new Dimension(800, 600));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
timer.start();
}
private static class ViewPanel extends JPanel implements Observer {
private final JButton item = new JButton();
private DataModel model;
private int index;
public ViewPanel(DataModel model, int i) {
this.model = model;
this.index = i;
this.add(item);
item.setText(String.valueOf(i));
item.setOpaque(true);
item.setBackground(new Color(model.get(index)));
model.addObserver(this);
}
#Override
public void update(Observable o, Object arg) {
int value = model.get(index);
item.setBackground(new Color(value));
}
}
private static class DataModel extends Observable {
private final Random rnd = new Random();
private final int[] data;
public DataModel(int n) {
data = new int[n];
fillData();
}
public void update() {
fillData();
this.setChanged();
this.notifyObservers();
}
public int get(int i) {
return data[i];
}
private void fillData() {
for (int i = 0; i < data.length; i++) {
data[i] = rnd.nextInt();
}
}
}
}

How to make JTextPane autoscroll only when scroll bar is at bottom and scroll lock is off?

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.

How would I go about highlighting an item in a JList? [duplicate]

I am trying to change JList rows dynamically. I need change nth row colour, highlight it(n is unknown during compilation). I saw a lot of examples with custom ListCellRenderer, but all were "static".
In other words I have JList with x rows. During runtime my "business logic" detects nth row is important. So I want make its background green, wait one second, and then make it white again. One more thing, don't wan change row selection.
What is the best way to do so?
Simple, set a custom ListCellRenderer to your JList using:
list.setCellRenderer(myListCellrenderer);
Now inside the overridden method getListCellRendererComponent() do something like this:
public Component getListCellRendererComponent(.....) {
Component c = super.getListCellRendererComponent();
c.setBackGround(Color.blue)
return c;
}
The above example assumed that your custom renderer overrid DefaultListCellRenderer
Based on ListDemo sample from SUN.
If you enter some text in the textfield which isn't in the list and you hit highlight it gets added.
If the text is in the list and you hit highlight the entry in the list gets temporarily highlighted blue.
Note the solution here with the match field is just for demo. For more correct implementation consider the other ideas proposed and consider using javax.swing.Timer
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class ListDemo extends JPanel {
private JList list;
private DefaultListModel listModel;
public String match = null;
private static final String hireString = "Highlight";
private JTextField employeeName;
public ListDemo() {
super(new BorderLayout());
listModel = new DefaultListModel();
listModel.addElement("Test1");
listModel.addElement("Test2");
listModel.addElement("Test3");
list = new JList(listModel);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setSelectedIndex(0);
list.setVisibleRowCount(5);
list.setCellRenderer(new MyListCellRenderer());
JScrollPane listScrollPane = new JScrollPane(list);
JButton hireButton = new JButton(hireString);
HireListener hireListener = new HireListener(hireButton);
hireButton.setActionCommand(hireString);
hireButton.addActionListener(hireListener);
hireButton.setEnabled(false);
employeeName = new JTextField(10);
employeeName.addActionListener(hireListener);
employeeName.getDocument().addDocumentListener(hireListener);
listModel.getElementAt(list.getSelectedIndex()).toString();
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane,
BoxLayout.LINE_AXIS));
buttonPane.add(Box.createHorizontalStrut(5));
buttonPane.add(employeeName);
buttonPane.add(hireButton);
buttonPane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
add(listScrollPane, BorderLayout.CENTER);
add(buttonPane, BorderLayout.PAGE_END);
}
class MyListCellRenderer extends JLabel implements ListCellRenderer {
public MyListCellRenderer() {
setOpaque(true);
}
public Component getListCellRendererComponent(JList paramlist, Object value, int index, boolean isSelected, boolean cellHasFocus) {
setText(value.toString());
if (value.toString().equals(match)) {
setBackground(Color.BLUE);
SwingWorker worker = new SwingWorker() {
#Override
public Object doInBackground() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) { /*Who cares*/ }
return null;
}
#Override
public void done() {
match = null;
list.repaint();
}
};
worker.execute();
} else
setBackground(Color.RED);
return this;
}
}
class HireListener implements ActionListener, DocumentListener {
private boolean alreadyEnabled = false;
private JButton button;
public HireListener(JButton button) {
this.button = button;
}
public void actionPerformed(ActionEvent e) {
String name = employeeName.getText();
if (listModel.contains(name)) {
match = name;
list.repaint();
employeeName.requestFocusInWindow();
employeeName.selectAll();
return;
}
if (name.equals("")) {
Toolkit.getDefaultToolkit().beep();
employeeName.requestFocusInWindow();
employeeName.selectAll();
return;
}
int index = list.getSelectedIndex();
if (index == -1)
index = 0;
else
index++;
listModel.insertElementAt(employeeName.getText(), index);
employeeName.requestFocusInWindow();
employeeName.setText("");
list.setSelectedIndex(index);
list.ensureIndexIsVisible(index);
}
public void insertUpdate(DocumentEvent e) {
enableButton();
}
public void removeUpdate(DocumentEvent e) {
handleEmptyTextField(e);
}
public void changedUpdate(DocumentEvent e) {
if (!handleEmptyTextField(e))
enableButton();
}
private void enableButton() {
if (!alreadyEnabled)
button.setEnabled(true);
}
private boolean handleEmptyTextField(DocumentEvent e) {
if (e.getDocument().getLength() <= 0) {
button.setEnabled(false);
alreadyEnabled = false;
return true;
}
return false;
}
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("ListDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JComponent newContentPane = new ListDemo();
newContentPane.setOpaque(true);
frame.setContentPane(newContentPane);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() { createAndShowGUI(); }
});
}
}
Your custom ListCellRenderer, which implements the method getListCellRendererComponent, will have access to both the JList and the value that it is redering. This gives you a couple options for how to determine when to paint the nth row green:
You could subclass JList and have the renderer ask it which color to use for the bg. The JList subclass could trigger a repaint when the business logic determines that it is time for the nth row to be green, and then start an Swing Timer to trigger a repaint returning the bg back to normal
When the business logic determines when you should show the row as green, you also have the option of setting state on the backing object of the row, and test it for that state within getListCellRendererComponent, setting the bg green if the state is correct. Again, you have the option of setting an Swing Timer to revert the state on the backing object.

Categories