How to fix JTextArea Scroll to bottom? - java

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.

Related

Why do keybindings stop working after pressing the corresponding button?

I'm creating a simple Calculator program using Java, Swing.
The keybindings work fine. Well... almost. I ran the software, pressed the number buttons and everything went well, as it should've. Then, I pressed some buttons using my mouse and still, everything is just fine up to this point.
The problem comes when, after pressing the buttons with my mouse the keybindings stop working.
Here's the code for pressing the number 0 (the code for the rest of the buttons are implemented in the same way).
actions[0] = new press0Action();
frame.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0, 0), "0");
frame.getRootPane().getRootPane().getActionMap().put("0", actions[0]);
private class press0Action extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
buttons[0].doClick();
}
}
private void buttonPressed0() {
buttons[0].addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
//Code goes here for pressin the button...
});
}
So why can't I use the keyboard after clicking on the buttons in the
GUI manually with a mouse?
How can I fix this problem?
Thank you for answering in advance! Feel free to add any suggestions for improvement.
P.S.: I've got a feeling it's something to do with the fact that I bound the keys to frame.getRootPane()
You registered your inputs for the root pane, and with no explicit condition. Instead try registering them for the entire window:
actions[0] = new press0Action();
frame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0, 0), "0");
frame.getRootPane().getRootPane().getActionMap().put("0", actions[0]);
Note the alternate getInputMap with argument.

Allow parent to handle mouse event

I added a JPanel to a JRadioButton - so that I may display whatever I want in the radio button.
This all worked as expected. But to allow for text wrapping, I used a JTextArea and added it to the JPanel contained within the radio button.
Now I have an issue where, if the user clicks on the JTextArea, then the JTextArea consumes the mouseEvent and as a result there is no response from the radio button (it doesn't 'select').
Is there a way get the JTextArea to ignore the mouse click, so that the parent may handle it instead?
I tried add the JTextArea's listeners to the radioButton instead.
I also tried to remove its listeners completely, but both these attempts failed.
Anyone have any suggestions?
Strong beware
Most JSomething are not meant to be used as containers even though it's possible - the outcome of doing it anyway is more or less visually and behaviourally undetermined!
That said, did it recently, to implement something similar to a Windows task dialog. If the requirement includes keeping the button clickable (and why else would you mis-use it as a container :-) the main problem (layout apart) is to make all added components completely mouse-transparent. Which is more difficult than can be expected. The minimum is to not allow adding of mouseListeners and disable the acceptance of mouseEvents:
final JTextArea area = new JTextArea("replacement ..") {
#Override
public synchronized void addMouseListener(MouseListener l) {
LOG.info("adding here ...?");
}
#Override
public synchronized void addMouseMotionListener(
MouseMotionListener l) {
}
#Override
public synchronized void addMouseWheelListener(
MouseWheelListener l) {
}
#Override
public void addNotify() {
disableEvents(AWTEvent.MOUSE_EVENT_MASK |
AWTEvent.MOUSE_MOTION_EVENT_MASK |
AWTEvent.MOUSE_WHEEL_EVENT_MASK);
super.addNotify();
}
};
Plus make sure it's not focusable
area.setEditable(false);
area.setFocusable(false);
area.setRequestFocusEnabled(false);
Plus unregister dragging and tooltips
ToolTipManager.sharedInstance().unregisterComponent(area);
area.setDragEnabled(false);
Nevertheless, there might still be surprises ahead, f.i. call the following twice (that is disable and enable again), which will internally re-enable mouseEvent:
area.setAutoscrolls(!area.getAutoscrolls());
So at the end of the day, we might get away with it - but never be entirely certain that we succeeded.
What about this? Create and add your own MouseListener to TextArea
JPanel p = new JPanel();
JTextArea t = new JTextArea("line \n line");
t.addMouseListener(new MyMouseListener());
p.add(t);
jRadioButton1.add(p);
jRadioButton1.addMouseListener(new MyRadioButtonMouseListener());
And in the MyMouseListener Dispatch event
private class MyMouseListener implements MouseListener {
#Override
public void mouseClicked(MouseEvent e) {
Component source = (Component) e.getSource();
source.getParent().getParent().dispatchEvent(e); // 2x getParent() because JTextArea->JPanel->JRadio
}
.
.
.
}
And finally RadioButtonMouseListener
private class MyRadioButtonMouseListener implements MouseListener {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("CLICK ON RADIOBUTTON !!");
}
.
.
.
}

Getting SWT StyledText widget to always scroll to its end

How do I get a SWT StyledText widget to always stay scrolled to the end it even as new lines of text gets appended to it?
I tried to look for some functions that could allow me to set the scroll position but I can't find any. There isn't a property that lets me do this either.
Simply add this line, after you've added text:
styledText.setTopIndex(styledText.getLineCount() - 1);
If you change the content of your StyledText on more than one place, use a listener on Modify, to not repeat yourself:
styledText.addListener(SWT.Modify, new Listener(){
public void handleEvent(Event e){
styledText.setTopIndex(styledText.getLineCount() - 1);
}
});
Another variation:
styledText.addModifyListener(new ModifyListener() {
#Override
public void modifyText(ModifyEvent e) {
styledText.setTopIndex(styledText.getLineCount() - 1);
}
});

Remove the listener for defaultButton in java

i have a Jframe application with the defaultbutton set to btnClose_ (Close Button: this button closes the window).
I have 2 textfields that must also fire an event when user clicks the Enter key on the textfields. What happens is that when I press the Enter key while the cursor is on the textfield, the event on the Close button is fired causing the window to close.
Is it possible to remove the listener of the default button if the Enter key is pressed on the textfield? Here's my code for the textfield listener
/**
* Receives the two textfield instance
*/
private void addFilterListener(JTextField txf) {
txf.addKeyListener(new KeyAdapter() {
/**
* Invoked when a key has been pressed.
*/
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
ActionListener al = btnClose_.getActionListeners()[0];
btnClose_.removeActionListener(al);
btnFilter_.doClick();
e.consume();
btnClose_.addActionListener(al);
}
}
});
}
private JButton getBtnClose(){
if(btnClose == null){
btnClose.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
getWindow().dispose();
}
});
}
return btnClose;
}
}
Where to start?
The first thing that springs out at me is the bad variable names. txf? What's wrong with proper words? textField or field, say. Or much better, a name descriptive of its purpose, not what it is.
Secondly, the first comment is wrong (not uncommon) and the second comment is redundant (already specified in the KeyListener interface, you don't need to try and half-heartedly specify it again).
Next up, low level key listeners tend not to work so well on Swing components (JComboBox being the most notorious example - it typically is implemented with child components). In general you can use JComponent.registerKeyboardAction (the API docs says this is obsolete but not deprecated, and to use more verbose code). For text components, you often want to play with the document (typically through DocumentFilter). In this particular case, looks like you just want to add an ActionListener.
Now doClick. It's a bit of an evil method. For one thing it blocks the EDT. It is probably the easiest way to make it look as if a button is pressed. From a programming logic point of view, it's best to keep away from modifying Swing components, when you can keep everything in your abstracted code.
Removing and adding listeners from components is generally a bad idea. Your code should determine what to do with an event including whether to ignore it. Do that at an appropriate point when handling the event. Don't duplicate state unnecessarily.
A potential issue is that the code seems to assume that there is precisely one action listener. There could be others. The code is not robust under unexpected behaviour. Set your components up at initialisation time, and you shouldn't need to refer to them again.
As far as I understood your question, you want that buttonClick should not get fired if Enter is pressed .
This won't fire doClick() if enter is pressed
if (e.getKeyCode() != KeyEvent.VK_ENTER) {
btnFilter_.doClick();
}
In the ActionListener of the close button, assuming you can change its code, don't close if one of the text fields have the focus.
public void actionPerformed(ActionEvent e) {
if (field1.hasFocus() || field2.hasFocus())
return; // don't close if text field has focus
frame.dispose();
}
If you can not change the ActionListener of the close button, add a FocusListener to the text fields. If one of them gets the focus, remove the default button. If the text field lost the focus, reset the default button.
FocusAdapter listener = new FocusAdapter() {
#Override
public void focusGained(FocusEvent e) {
frame.getRootPane().setDefaultButton(null);
}
#Override
public void focusLost(FocusEvent e) {
frame.getRootPane().setDefaultButton(close);
}
};
field1.addFocusListener(listener);
field2.addFocusListener(listener);
This should be better than depending on the listeners being called in the correct sequence - it is of no avail to remove the listener if it was already called...

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