Prepend text to JTextArea without scrolling up (java swing) - java

If I want to prevent the JTextArea from scrolling to the bottom when I add text to the end, the solution is very simple: Just set the caret's update policy to DefaultCaret.NEVER_UPDATE before calling the JTextArea's append method. I am trying to do the same thing (load the text without scrolling), but for prepending text instead of appending text.
I have tried lots of things. One of them is this, but it doesn't work:
public void loadMoreUp(){
caret = (DefaultCaret)ta.getCaret(); // ta is a JTextArea
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); //doesn't work
String s = "The new text\n";
ta.setText(s + ta.getText()); // I have also tried with ta.getDocument().insertString(0,s,null)
}
The behavior I want is that "The new text" gets prepended to the top, but the JTextArea doesn't scroll up with it. "The new text" should not be visible unless the user manually scrolls up to see it.
How can I prepend text to the top of a JTextArea, without it scrolling up? My JTextArea is in a JScrollPane if that is relevant.

So it turns out that making your JTextArea uneditable is what's keeping the scrollbar at the top. I don't think prepending text is moving it -- it's more the case that it never moves at all.
If the goal is simply to keep the scrollbar scrolled all the way down the scrollpane, however, all you really have to do is set the caret position, and no one should be the wiser.
You can even set the caret position to some previous position by saving .getCaretPosition() and using that value later.
textArea.insert("Mein Hund frisst Nuesse\n", 0);
textArea.setCaretPosition(textArea.getDocument().getLength());
You can see my full example here, which you can use to reconcile against your implementation.
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import java.awt.Dimension;
import javax.swing.JButton;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Foo extends JFrame implements ActionListener
{
JTextArea textArea;
JScrollPane scrollPane;
JButton button;
public Foo()
{
textArea = new JTextArea();
textArea.setEditable(false);
textArea.setText("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\n");
scrollPane = new JScrollPane(textArea);
scrollPane.setPreferredSize(new Dimension(400, 200));
button = new JButton("Prepend");
button.addActionListener(this);
this.add(button, BorderLayout.PAGE_END);
this.add(scrollPane, BorderLayout.PAGE_START);
}
public void actionPerformed(ActionEvent e)
{
if(e.getSource() == button)
{
textArea.insert("Mein Hund frisst Nuesse\n", 0);
textArea.setCaretPosition(textArea.getDocument().getLength());
//textArea.setText("Mein Hund frisst Nuesse\n" + textArea.getText());
}
}
public static void main(String[] args)
{
Foo f = new Foo();
f.setPreferredSize(new Dimension(500, 300));
f.pack();
f.setVisible(true);
}
}

The following code seems to work:
public void loadMoreUp(){
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
String s = "The new text\n";
JScrollBar vbar = scrollPane.getVerticalScrollBar();
int diff = vbar.getMaximum() - vbar.getValue();
try{
ta.getDocument().insertString(0, s, null);
}
catch(BadLocationException e){
logger.error("Bad Location");
}
SwingUtilities.invokeLater(new Runnable(){
public void run(){
vbar.setValue(vbar.getMaximum()-diff);
}
});
}
The basic idea is to remember the position relative to the END of the document (vbar.getMaximum() - vbar.getValue()), and then restore this value after prepending the text.
invokeLater is needed, otherwise getMaximum runs before its value gets updated. The drawback with this method is that invokeLater makes the text briefly flicker.

Related

JScrollPane not appearing on JTextArea

I'm trying to add the JScrollPane to my JTextArea, but somehow, it won't appear.
I've tried resizing it according to the dimension of the JTextArea, but it doesn't seem to work. Also, notice that I'm using the null layout because I want the full-on flexibility of displaying certain buttons and panels at a pinpoint location.
import java.awt.Dimension;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.JTextArea;
public class PaneTester{
private static JFrame frame;
private static JPanel panel;
private static JScrollPane scrollPane;
private static JTextArea notificationBox;
public static void main (String [] args){
stage1();
stage2();
}
private static void stage1(){
createFrame();
createPanel();
frame.getContentPane().add(panel);
panel.setVisible(true);
frame.setVisible(true);
}
private static void stage2(){
generateNotificationBox();
}
private static void createFrame(){
frame = new JFrame();
frame.setSize(new Dimension(900,700));
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(true);
}
private static void createPanel(){
panel = new JPanel();
panel.setLayout(null);
generateGridButtons();
}
private static void generateGridButtons(){
short y = 0;
for(short i=0;i<4;i++){
y += 60;
short x = 500;
for(short j=0;j<5;j++){
JButton gridButton = new JButton();
gridButton.setBounds(x, y,120,60);
panel.add(gridButton);
x += 140;
}
}
}
public static void generateNotificationBox(){
notificationBox = new JTextArea(10,10);
notificationBox.setBounds(25, 25, 200, 400);
scrollPane = new JScrollPane(notificationBox, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS );
Dimension d = new Dimension(notificationBox.getPreferredSize());
scrollPane.getViewport().setPreferredSize(d);
scrollPane.getViewport().add(notificationBox);
panel.add(notificationBox);
panel.repaint();
}
}
Stop mucking with setBounds and setPreferredSize, you're just making live more difficult for your self.
If you want to affect the size of JTextArea (and the viewable area of the JScrollPane) have a look at the JTextArea constructor JTextArea(int rows, int columns), which will allow you to specify the number of rows/columns you want the JTextArea to default to, and which will allow the JTextArea to calculate it's preferredSize based on the current font's metrics in more stable cross platform way
Your core problem, however, is right here...
scrollPane.getViewport().add(notificationBox);
panel.add(notificationBox);
You add the notificationBox to the JScrollPanes JViewport, which is good, but then you add notificationBox to the panel, which will remove it from the JScrollPane's JViewport, which is bad
Instead, add the JScrollPane to the panel
scrollPane.getViewport().add(notificationBox);
panel.add(scrollPane);
You're also making overuse of static. I'd highly recommend you take the time to reduce static down to it's absolute minimum required usage, this will probably mean that rather then constructing the UI in the main method, you have a "main" class which you can insatiate (from main) which will perform the initial setup - IMHO
I've tried that. I think someone else suggested that from another post, but when I tried that, it just took away the JTextArea completely from the panel
Get rid of panel.setLayout(null); and start making use of appropriate layout managers and compound layouts. Start by having look at Laying Out Components Within a Container for more details

JButton not changing size

I've researched the ways to change the size of a jbutton to be displayed on a JFrame.
I am trying both the button.setSize(200,200) and button.setPreferredSize(new Dimension(200,200)), but it does not change. Here's the code:
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Index extends JFrame{
private String title = "This is the motherfucking title";
Dimension dim = new Dimension(500,500);
public Index(){
this.setResizable(false);
this.setTitle(title);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(dim);
this.getContentPane().setBackground(Color.BLACK);
JButton button = new JButton("Button");
button.setSize(200,200);
this.add(button);
}
public static void main(String args[]){
Index ih = new Index();
ih.setVisible(true);
}
}
Here's the result: http://i.imgur.com/Llj0pfo.png
What am I doing wrong?
try this:
JButton button = new JButton("Button");
button.setSize(200,200);
getContentPane().setLayout(null);
getContentPane().add(button);
setVisible(true);
inside your constructor.
this.add(button);
You are adding the button to the content pane of the frame. By default the content uses a BorderLayout and the component is added to the CENTER. Any component added to the CENTER will automatically get the extra space available in the frame. Since you set the size of the frame to (500, 500) there is lots of space available.
As a general rule you should NOT attempt to set the preferred size of a component, since only the component know how big it should be in order to paint itself properly. So your basic code should be:
JButton button = new JButton("...");
frame.add(button);
frame.pack();
frame.setVisible(true);
Now the button will be at its preferred size. However, the button will change size if you resize the frame. If you don't want this behaviour, then you need to use a different Layout Manager.
Use SwingUtilities.invokeLater(); create your Index() inside it, then call setVisible(true); at the end of constructor. At the same time remeber that by default JFrame uses BorderLayout.
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new Index();
}
});

After changing JLabel text it will not reposition

I have a JDialog with a title written at the top. I call this JDialog for two different cases and if it is not the default I change the text to something else. This works fine but the position is then too far to the right.
I have tried numerous methods such as:
TitleText.setText("Edit Fuse");
TitleText.setAlignmentY(JLabel.CENTER_ALIGNMENT);
//TitleText.setHorizontalAlignment(JDialog.);
//TitleText.setLayout(new FlowLayout(FlowLayout.LEFT));
None of them even move the text. I am using a Free Design Layout for the entire JDialog. If I must I'll just create another JLable and hide/unhide but I thought this would be simple. Anyone know how to do this?
I am using a Free Design Layout for the entire JDialog
Don't do this if you want the title JLabel's text to be at the top and be centered. Instead have the JDialog's main JPanel use BorderLayout, and add the JLabel to it BorderLayout.PAGE_START, also known as BorderLayout.North.
The main JPanel can then hold the rest of your GUI, likely in its own JPanel, using its own layout manager, and other JPanels, in its BorderLayout.CENTER position.
Also, don't use JLabel.CENTER_ALIGNMENT, a float, but rather use JLabel.CENTER, an int, which is the appropriate parameter for the setHorizontalAlignment(...) method.
Finally, I must give you an unsolicitated side recommendation to be sure that your variable names begin with a lower-case letters and not upper case letters so that they comply with Java naming rules. This is important if you want others, such as folks here who'd like to help you, to rapidly understand your code.
For example:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
#SuppressWarnings("serial")
public class LayoutExample extends JPanel {
private static final float SIZE = 32;
private static final int TIMER_DELAY = 2000;
private String[] TITLE_STRINGS = { "Title 1", "Title 2",
"Some Random Title", "Fubars Rule!", "Snafus Drool!" };
private int titleIndex = 0;
private JLabel titleLabel = new JLabel(TITLE_STRINGS[titleIndex],
JLabel.CENTER);
public LayoutExample() {
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, SIZE));
setLayout(new BorderLayout());
add(titleLabel, BorderLayout.PAGE_START);
// the rest of your GUI could be added below
add(Box.createRigidArea(new Dimension(500, 300)), BorderLayout.CENTER);
new Timer(TIMER_DELAY, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
titleIndex++;
titleIndex %= TITLE_STRINGS.length;
titleLabel.setText(TITLE_STRINGS[titleIndex]);
}
}).start();
}
private static void createAndShowGui() {
JFrame frame = new JFrame("LayoutExample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new LayoutExample());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}

How to effectively add a call back method for the JPanel.add(component) method?

edit: now solved, but can't mark as accepted for two days
In my class I have a JScrollPanel and that has a JPanel inside of it too.
My code resembles something like this:
import java.awt.Color;
import java.awt.Container;
import javax.swing.*;
public class MyClass {
private JPanel p;
private JScrollPane s;
private Container contentPane;
public MyClass(Container contentPane) {
this.contentPane = contentPane;
this.p = new JPanel();
this.p.setBackground(Color.WHITE);
BoxLayout boxLayout = new BoxLayout(this.p, BoxLayout.Y_AXIS);
this.p.setLayout(boxLayout);
this.s = new JScrollPane(this.p);
this.s.setSize(400, 364);
this.contentPane.add(this.s);
}
public final JLabel makeJLabel(String message) {
JLabel jLabel = new JLabel("<html><p style=\"padding-left:15px;padding-right:15px;width:280px;\">" + message.replaceAll("(\r\n|\n)", "<br />") + "</p></html>");
/*
some stuff here to calculate pref/max size and add an imageicon
*/
p.add(jLabel);
this.p.revalidate();
this.s.revalidate(); //just added because the above line made no effect
scrollToBottom();
return jLabel;
}
public void scrollToBottom() {
JScrollBar vertical = s.getVerticalScrollBar();
vertical.setValue(vertical.getMaximum());
}
}
Elsewhere in my class I have a method which adds a JLabel to the JPanel. This actual method is quite long, so I wont post it all, but this is the code which adds it to the panel: p.add(jLabel1);
All of these JLabels are added in a vertical fashion thanks to the Box Layout.
After the JLabel has been added to the JPanel I want the JScrollPane to scroll to the bottom. But this can't be done until after the JPanel has actually been drawn (painted?) onto JPanel. Otherwise I get this result:
So what I want to do is add some form of listener to the JPanel which detects when my JLabel has been painted to it, so that I can tell my JScrollPane to scroll to the bottom. I have already written a method which scrolls the pane to the bottom, but I don't have anywhere suitable to call it from yet.
Does anyone have any ideas on this please? Thanks.
I'm assuming you just want the label to be visible in the scrollpane so I would gues you should be able to do something like:
panel.add( label );
panel.revalidate();
label.scrollRectToVisible( label.getBounds() );
Or if you really do want to just scroll bo the bottom then you would do something like:
panel.revalidate();
scrollPane.getVerticalScrollBar().setValue( getVerticalScrollBar().getMaximum() );
Both of these answers assume the GUI is already visible.
The first part of Rob's answer is the way to go - the missing piece is to wrap the scrollRectToVisible into SwingUtilities.invokeLater. Doing so delays the scrolling until all pending events are processed, that is until all internal state is updated. A code snippet (in swingx test support speak, simply replace the frame creation and scrollpane wrapping with manually created code)
final JComponent panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
for(int i = 0; i < 10; i++)
panel.add(new JLabel("initial message " + i));
JXFrame frame = wrapWithScrollingInFrame(panel, "scroll to bottom");
Action action = new AbstractAction("addMessage") {
int count;
#Override
public void actionPerformed(ActionEvent e) {
final JLabel label = new JLabel("added message " + count++);
panel.add(label);
panel.revalidate();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.scrollRectToVisible(label.getBounds());
}
});
}
};
frame.add(new JButton(action), BorderLayout.SOUTH);
show(frame);
Scratched my head for ages over this, but after asking the question I finally figured it out.
To solve my problem all I need to do was listen for a change in value in the JScrollPane's scroll bar. If it changed, done some calculations, and scroll to the bottom if necessary.
Care has to be taken to ensure that you're not overriding the user moving the scroll bar however.
In particular you're looking at the track which is an AdjustmentEvent. This event is also fired when a user moves the scroll bar.
In order to allow the user to scroll without forcibly scroll it to the bottom, I always keep track of the maximum scroll bar value. If when track is fired the new max value is higher than the current one then a new item has been added and we should think about scrolling to the bottom. If the values are equal then the user is scrolling the scroll bar and we do nothing.
The event listeners can be found on this website and can be make to work very easily: Listening for Scrollbar Value Changes in a JScrollPane Container
import java.awt.Color;
import java.awt.Container;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import javax.swing.*;
public class MyClass {
private JPanel p;
private JScrollPane s;
private Container contentPane;
private int scrollBarMax;
public MyClass(Container contentPane) {
this.contentPane = contentPane;
this.p = new JPanel();
this.p.setBackground(Color.WHITE);
BoxLayout boxLayout = new BoxLayout(this.p, BoxLayout.Y_AXIS);
this.p.setLayout(boxLayout);
this.s = new JScrollPane(this.p);
this.s.setSize(400, 364);
this.contentPane.add(this.s);
this.s.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener(){
#Override
public void adjustmentValueChanged(AdjustmentEvent evt) {
if (evt.getValueIsAdjusting()) {
return;
}
if (evt.getAdjustmentType() == AdjustmentEvent.TRACK) {
if (scrollBarMax < s.getVerticalScrollBar().getMaximum()) {
if ((s.getVerticalScrollBar().getValue() + s.getVerticalScrollBar().getSize().height) == scrollBarMax) {
//scroll bar is at the bottom, show the last added JLabel
scrollBarMax = s.getVerticalScrollBar().getMaximum();
scrollToBottom();
} else {
//scroll bar is not at the bottom, user has moved it
scrollBarMax = s.getVerticalScrollBar().getMaximum();
}
}
}
}
});
scrollBarMax = s.getVerticalScrollBar().getMaximum();
}
public final JLabel makeJLabel(String message) {
JLabel jLabel = new JLabel("<html><p style=\"padding-left:15px;padding-right:15px;width:280px;\">" + message.replaceAll("(\r\n|\n)", "<br />") + "</p></html>");
/*
some stuff here to calculate pref/max size and add an imageicon
*/
p.add(jLabel);
scrollBarMax = s.getVerticalScrollBar().getMaximum();
return jLabel;
}
public void scrollToBottom() {
JScrollBar vertical = s.getVerticalScrollBar();
vertical.setValue(vertical.getMaximum());
}
}

What text input component last had the focus?

Suppose I have a Java application that has more than one component in which you can enter text. Now suppose this application also has a dialog that lets you insert a single character (like the dialog in Word that comes up when you select Insert from the Edit menu) into those components. You want it to insert the character into whichever text component last had the focus.
But how do you know which text component last had the focus?
I could keep track of this manually, by having each text component report to the application whenever it gets the focus and then have the application insert the new character into whichever component that last had the focus.
But this must be a common problem (consider Paste buttons in tool bars---how does it know where to paste it into?). Is there something already built in to Swing that lets you get a handle to the last text component that had the focus? Or do I need to write this myself?
Is there something already built in to Swing that lets you get a handle to the last text component that had the focus?
You create an Action that extends TextAction. The TextAction class has a method that allows you to obtain the last text component that had focus.
Edit:
You can create your own Action and do whatever you want. The Action can then be added to any JMenuItem or JButton. For example:
class SelectAll extends TextAction
{
public SelectAll()
{
super("Select All");
}
public void actionPerformed(ActionEvent e)
{
JTextComponent component = getFocusedComponent();
component.selectAll();
}
}
If you just want to insert a character at the caret position of the text field then you can probably just do
component.replaceSelection(...);
Edit 2:
I don't understand what the confusion is with this answer. Here is a simple example:
select some text
use the mouse to click on the check box
tab or use the mouse to click on the "Cut" button
It doesn't matter that the text field doesn't currently have focus when the Action is invoked. The TextAction tracks the last text component that had focus.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
public class TextActionTest extends JFrame
{
JTextField textField = new JTextField("Select Me");
JTabbedPane tabbedPane;
public TextActionTest()
{
add(textField, BorderLayout.NORTH);
add(new JCheckBox("Click Me!"));
add(new JButton(new CutAction()), BorderLayout.SOUTH);
}
public static void main(String[] args)
{
TextActionTest frame = new TextActionTest();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
class CutAction extends TextAction
{
public CutAction()
{
super("Click to Cut Text");
}
public void actionPerformed(ActionEvent e)
{
JTextComponent component = getFocusedComponent();
// JTextComponent component = getTextComponent(e);
component.cut();
}
}
}
Just like suggested by #lesmana (+1 for that).
Here you have an example that shows that on focusLost the focus listener returns the previously focused component.
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Focusing
{
public static void main(String[] args)
{
JPanel p = new JPanel();
JTextField tf1 = new JTextField(6);
tf1.setName("tf1");
p.add(tf1);
JTextField tf2 = new JTextField(6);
tf2.setName("tf2");
p.add(tf2);
FocusListener fl = new FocusListener()
{
#Override
public void focusGained(FocusEvent e)
{
System.out.println("focusGained e.getSource().c=" + ((JComponent) e.getSource()).getName());
}
#Override
public void focusLost(FocusEvent e)
{
System.out.println("focusLost e.getSource().c=" + ((JComponent) e.getSource()).getName());
}
};
tf1.addFocusListener(fl);
tf2.addFocusListener(fl);
JPanel contentPane = new JPanel();
contentPane.add(p);
JFrame f = new JFrame();
f.setContentPane(contentPane);
f.setSize(800, 600);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
}
All the best, Boro.
I've never done this directly, but you could look into the FocusEvents and the Focus Subsystem.
Hopefully there is something in the Focus Subsystem that would fire events that you could listen for.
You can register a FocusListener to every text component. The FocusEvent object has a reference to the last component which had focus.

Categories