Java - Scroll to specific text inside JTextArea - java

I'm trying to implement a feature inside the current program that I'm writing and I wanna learn how to scroll down to specific text inside a JTextArea. For example, lets say I have the following:
JTextArea area = new JTextArea(someReallyLongString);
someReallyLongString would represent a paragraph, or a very large piece of text (in which the vertical scrollbar would be visible). And so what I am trying to do is scroll down to specific text within that text area. For example, lets say someReallyLongString contained the word "the" near the middle of the scrollbar (meaning this word is not visible), how would I scroll down to that specific text?
Thanks, any help would be greatly appreciating.

This is a VERY basic example. This basically walks the document to find the position of the word within the document and ensures that the text is moved to the viewable area.
It also highlights the match
public class MoveToText {
public static void main(String[] args) {
new MoveToText();
}
public MoveToText() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new FindTextPane());
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class FindTextPane extends JPanel {
private JTextField findField;
private JButton findButton;
private JTextArea textArea;
private int pos = 0;
public FindTextPane() {
setLayout(new BorderLayout());
findButton = new JButton("Next");
findField = new JTextField("Java", 10);
textArea = new JTextArea();
textArea.setWrapStyleWord(true);
textArea.setLineWrap(true);
Reader reader = null;
try {
reader = new FileReader(new File("Java.txt"));
textArea.read(reader, null);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (Exception e) {
}
}
JPanel header = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
header.add(findField, gbc);
gbc.gridx++;
header.add(findButton, gbc);
add(header, BorderLayout.NORTH);
add(new JScrollPane(textArea));
findButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Get the text to find...convert it to lower case for eaiser comparision
String find = findField.getText().toLowerCase();
// Focus the text area, otherwise the highlighting won't show up
textArea.requestFocusInWindow();
// Make sure we have a valid search term
if (find != null && find.length() > 0) {
Document document = textArea.getDocument();
int findLength = find.length();
try {
boolean found = false;
// Rest the search position if we're at the end of the document
if (pos + findLength > document.getLength()) {
pos = 0;
}
// While we haven't reached the end...
// "<=" Correction
while (pos + findLength <= document.getLength()) {
// Extract the text from teh docuemnt
String match = document.getText(pos, findLength).toLowerCase();
// Check to see if it matches or request
if (match.equals(find)) {
found = true;
break;
}
pos++;
}
// Did we find something...
if (found) {
// Get the rectangle of the where the text would be visible...
Rectangle viewRect = textArea.modelToView(pos);
// Scroll to make the rectangle visible
textArea.scrollRectToVisible(viewRect);
// Highlight the text
textArea.setCaretPosition(pos + findLength);
textArea.moveCaretPosition(pos);
// Move the search position beyond the current match
pos += findLength;
}
} catch (Exception exp) {
exp.printStackTrace();
}
}
}
});
}
}
}

This should work:
textArea.setCaretPosition(posOfTextToScroll);
You can get the posOfTextToScroll by the Document model. Read about it, in the Javadoc.

First get the text you set in the text area and build an index using a map to hold the character and the position you find it on.
Based on this the previous answer suggested used the setCaretPosition using the value retrieved from the map.

Add on to the comment by MadProgrammer:
scrollRectToVisible(viewRect) is deprecated as of Java SE9, and it has been replaced by scrollRectToVisible2D(viewRect)
The proper way to get the text to display without using deprecated functions would be:
java.awt.geom.Rectangle2D view = area.modelToView2D(pos); // View where pos is visible
area.scrollRectToVisible(view.getBounds()); // Scroll to the rectangle provided by view
area.setCaretPosition(pos); // Sets carat position to pos

Related

Why do my buttons wont show up?

I want to build a bingo got the following source code, which should create a JFrame with 25 buttons placed in a 5x5 matrix. But none of my button gets drawn on the window in any kind.
I ve created a Jpanel on which the buttons are placed, the locations and such are not specific, finetuning will come later, first thing is to even get them drawn on the window.
Bingo Buttons is a class which extends JFrame and simply adds two methods, one to toggle its status from true to false and the other way around and also an method (isSet) to check if the buttons is currently true or false.
bingoField is an String Array which holds nothing but the data which the buttons should get.
I dont get why it does nothing, please help me out. Any kind of help is highly appreciated!
public class BingoFrame extends JFrame {
public static final int BINGOSIZE=25;
public static final int BUTTON_X=50;
public static final int BUTTON_Y=50;
public BingoFrame() {
setResizable(false);
String[] bingoField = null;
BingoButton[] buttons=new BingoButton[25];
try {
bingoField = Utils.getRandomBingoField("Test");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
this.setTitle("BS Bingo");
this.setResizable(false);
this.setLocation(50, 50);
this.setSize(600, 800);
this.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
getContentPane().setLayout(null);
JPanel buttonPanel = new JPanel();
buttonPanel.setBounds(0, 0, 594, 772);
getContentPane().add(buttonPanel);
buttonPanel.setLayout(null);
for(int i=0;i<BINGOSIZE;i++) {
buttons[i] = new BingoButton("Text");
}
//decorate buttons and add an action listener
for(int i=0;i<BINGOSIZE;i++) {
final BingoButton temp = buttons[i];
temp.setText(bingoField[i]);
temp.setBackground(Color.white);
temp.setForeground(Color.blue);
temp.setPreferredSize(new Dimension(BUTTON_X,BUTTON_Y));
temp.addActionListener(new ActionListener() {
boolean toggle = false;
#Override
public void actionPerformed(ActionEvent e) {
if (!temp.isSet()) {
temp.setBackground(Color.blue);
temp.setForeground(Color.white);
} else {
temp.setBackground(Color.white);
temp.setForeground(Color.blue);
}
temp.toggle();
}
});
buttons[i]=temp;
}
//set Location for the buttons
for(int i=0;i<5;i++) {
buttons[i].setLocation(100,(50*i)+10*(i+1));
}
for(int i=5;i<10;i++) {
buttons[i].setLocation(160,(50*i)+10*(i+1));
}
for(int i=10;i<15;i++) {
buttons[i].setLocation(220,(50*i)+10*(i+1));
}
for(int i=15;i<20;i++) {
buttons[i].setLocation(280,(50*i)+10*(i+1));
}
for(int i=20;i<25;i++) {
buttons[i].setLocation(340,(50*i)+10*(i+1));
}
//add buttons to the panel
for(int i=0;i<BINGOSIZE;i++) {
buttonPanel.add(buttons[i]);
}
this.setVisible(true);
I got the answer.
I ve changed the Layout of the Panel to Grid Layout. This alligns the buttons just where they should be in a 5x5 matrix and also with the wanted gap between. This makes also the code for the positioning completly obsolete.
By simply changing the Layout to GridLayout all of my Problems were gone.

JTable data not getting updated on combobox selection(data is coming from file)

I have a mainPanel ,there is a button in mainPanel, on the click of that button, textPanel
becomes active ,inside textpanel ,only a comobox appears at first,when you select any item from combobox a table displayed with the data corresponding to that
mainPanel->textPanel->Panelc2->F1Table
mainPanel->textPanel->Panelc1->box2
When I do this first time it works properly ,but when I again select some item then it does not refresh the table and show the same data.The problem is not related to data,I think there is something I am missing in java swings.I am new to Swings.Please give some suggestion,If anybody can look into it
Code:
public void box2actionPerformed(ActionEvent event)
{
try
{
String str=(String)box2.getSelectedItem();
Pattern pat=Pattern.compile("//[(.)*//]");
Matcher patMatcher=pat.matcher(str);
Configuration conf = new Configuration();
conf.addResource(new Path("/usr/local/hadoop/conf/core-site.xml"));
FileSystem fs = FileSystem.get(conf);
String location="hdfs://localhost:54310/user/a.txt";
modelF1 = new DefaultTableModel();
F1Table=new JTable(modelF1);
modelF1.addColumn("Data");
System.out.println(modelF1.getRowCount());
if (modelF1.getRowCount() > 0) {
for (int i = modelF1.getRowCount() - 1; i > -1; i--) {
modelF1.removeRow(i);
System.out.println(i);
}
}
//modelF1.fireTableDataChanged();
if (modelF1.getRowCount() ==-1) {
System.out.println("no data");
F1Table.removeAll();
}
rowCount=0;
panelC2=new JPanel();
panelC2.repaint();
panelC2.revalidate();
textPanel3.revalidate();
textPanel3.repaint();
mainPanel.revalidate();
mainPanel.repaint();
Path perr=new Path(location);
BufferedReader breader1=new BufferedReader(new InputStreamReader(fs.open(perr)));
String line="";
modelF1 = new DefaultTableModel();
modelF1.fireTableDataChanged();
F1Table=new JTable(modelF1);
modelF1.addColumn("Data");
if(patMatcher.find())
{
String patVal=str.substring(patMatcher.start(), patMatcher.end());
System.out.println(patVal);
while((line=breader1.readLine()) != null)
{
Matcher patMatcherl=pat.matcher(line);
if(patMatcherl.find())
{
String patVall=line.substring(patMatcherl.start(), patMatcherl.end());
System.out.println(patVall);
if(patVal.equals(patVall))
{
modelF1.addRow(new Object[]{line});
}
}
}
}
breader1.close();
modelF1.fireTableDataChanged();
F1scrollPanel = new JScrollPane(F1Table, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
F1Table.setPreferredSize(new Dimension(1000, 450));
F1Table.setSize(1000,450);
F1scrollPanel.setPreferredSize(new Dimension(1000, 450));
F1scrollPanel.setSize(1000,450);
panelC2.setPreferredSize(new Dimension(1000, 520));
panelC2.setSize(1000,520);
progressBar.setValue(100);
F1Table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
panelC2.add(F1,BorderLayout.NORTH);
panelC2.add(F1scrollPanel,BorderLayout.SOUTH);
textPanel3.add(panelC2,BorderLayout.CENTER);
textPanel3.revalidate();
textPanel3.repaint();
mainPanel.revalidate();
mainPanel.repaint();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void buttonclickactionPerformed(ActionEvent event)
{
try
{
String filepath=pathonconsole.getText();
filepath=filepath.trim();
String filename=filepath.substring(filepath.lastIndexOf('/') + 1);
int i=0;
textPanel3=new JPanel();
textPanel3.setPreferredSize(new Dimension(1100,600));
textPanel3.setSize(1100,600);
box2=new JComboBox();
Configuration conf = new Configuration();
conf.addResource(new Path("/usr/local/hadoop/conf/core-site.xml"));
FileSystem fs = FileSystem.get(conf);
String location="hdfs://localhost:54310/user/b.txt";
Path pcor=new Path(location);
BufferedReader breader1=new BufferedReader(new InputStreamReader(fs.open(pcor)));
String line="";
while((line=breader1.readLine())!=null)
{
box2.addItem(line);
}
breader1.close();
box2.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
box2actionPerformed(evt);
}
});
panelC1=new JPanel();
box2.setPreferredSize(new Dimension(150, 30));
box2.setSize(150,30);
panelC1.setPreferredSize(new Dimension(1100, 40));
panelC1.setSize(1100, 40);
panelC1.add(box2,BorderLayout.NORTH);
FlowLayout obj=new FlowLayout(FlowLayout.CENTER);
textPanel3.setLayout(obj);
textPanel3.add(panelC1);
mainPanel.add(textPanel3,BorderLayout.CENTER);
System.out.println("added in main panel");
textPanel3.setVisible(true);
System.out.println("done");
}
catch (Exception e) {
e.printStackTrace();
}
}
panelC2=new JPanel();
panelC2.repaint();
panelC2.revalidate();
textPanel3.revalidate();
textPanel3.repaint();
mainPanel.revalidate();
mainPanel.repaint();
The above code is unnecessary. First you create a new panel, but you don't add anything to the panel and you don't add the panel to any other component so there is no need to revalidate()/repaint() the panel. Same for the textPanel3 and mainPanel. You haven't made any changes so you don't need the code.
Whenever you do dynamically add/remove components then you only need to revalidate()/repaint() the top level container. In this case that would be the mainPanel, because it will then revalidate() the textPanel3 and panelC2.
F1scrollPanel = new JScrollPane(F1Table, ...)
Another general comment is don't keep adding/removing components for a panel. The better approach is to create the scrollpane with an empty JTable. Then when you want to change the data in the table you just create a new TableModel and update the existing table by using the table.setModel(...) method. This way the table will automatically repaint itself and you don't even have to worry about revalidate() and repaint().
Thanks Andrew and Camickr,I tried making the compilable solution, In the process of making it I got the solution. As Camickr said,I only need to update model values.Earlier I was defining all panelc2,table and scrollpane in box2actionperform method.So every time the select index change event fires it need to recreate everything or some problem arise in that part.I have changed the definition location only,I defined the panelc2=new panelc2() and all other component in buttonclickactionPerformed function.I am only updating the values in box2actionperform method.That solved the purpose.Thanks a lot to both of you.:)

Generalize several text fields for selecting the entire text when focus is gained

I want each of several text fields in a panel to have center alignment and to have a focus listener that will select the entire field when focus is gained.
txtSelection = new JTextField("", 9);
txtInclusion = new JTextField("", 9);
txtExclusion = new JTextField("", 9);
...
I know how to do it one by one:
txtSelection.setHorizontalAlignment(JTextField.CENTER);
txtSelection.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent e) {
txtSelection.select(0, 9);
}
});
But I'd rather not repeat that structure for all the text fields.
I tried this (and a couple of varieties) but the code seems to do nothing:
pnlConditions = new JPanel();
pnlConditions.setLayout(new GridBagLayout());
for(final Component c : pnlConditions.getComponents()) // compiler insisted on 'final'
{
((JTextField)c).setHorizontalAlignment(JTextField.CENTER);
if(c instanceof JTextField)
{
c.addFocusListener
(new FocusAdapter()
{
#Override public void focusGained(FocusEvent e)
{
((JTextField)c).select(0,9); // compiler suggested cast to JTextComponent; no difference
}
}
);
}
}
The entire field is not selected; alignment is left (the default). I guess my first clues that it wouldn't work are the compiler's insisting that c must be final in the for and for suggesting that the cast should have been to JTextComponent in the statement with select; but it didn't make that suggestion on the statement with setHorizontal....
I'm sure this can be done; but how?
Here's the entire method containing the snippets above:
static void makePnlConditions(){
JLabel lblSelections = new JLabel("Select ONLY combos with ALL of:");
JLabel lblInclusions = new JLabel("DE-select combo NOT containing one or more of:");
JLabel lblExclusions = new JLabel("DE-select combo containing ANY of:");
txtSelection = new JTextField("", 9);
txtInclusion = new JTextField("", 9);
txtExclusion = new JTextField("", 9);
pnlConditions = new JPanel();
pnlConditions.setLayout(new GridBagLayout());
for(final Component c : pnlConditions.getComponents())
{
((JTextField)c).setHorizontalAlignment(JTextField.CENTER);
if(c instanceof JTextField)
{
c.addFocusListener
(new FocusAdapter()
{
#Override public void focusGained(FocusEvent e)
{
((JTextField)c).select(0,9);
}
}
);
}
}
pnlConditions.add(lblSelections);
pnlConditions.add(txtSelection);
pnlConditions.add(lblInclusions);
pnlConditions.add(txtInclusion);
pnlConditions.add(lblExclusions);
pnlConditions.add(txtExclusion);
}
First add the components into JPanel otherwise pnlConditions.getComponents() will return an empty array and no listener will be added for JTextField.
If you want to select entire text then use JTextComponent#selectAll() method.
Sample code:
JPanel pnlConditions = new JPanel();
pnlConditions.setLayout(new GridBagLayout());
pnlConditions.add(lblSelections);
pnlConditions.add(txtSelection);
pnlConditions.add(lblInclusions);
pnlConditions.add(txtInclusion);
pnlConditions.add(lblExclusions);
pnlConditions.add(txtExclusion);
for (final Component c : pnlConditions.getComponents()) {
if (c instanceof JTextField) {
((JTextField) c).setHorizontalAlignment(JTextField.CENTER);
...
}
}
I want each of several text fields in a panel to have center alignment and to have a focus listener that will select the entire field when focus is gained.
Create a custom class that extends JTextField, provide all default implementation and use it everywhere in your application to make it centralized.
Sample code:
class MyJTextField extends JTextField {
// Initialization block that is called for all the constructors
{
this.addFocusListener(new FocusAdapter() {
#Override
public void focusGained(FocusEvent e) {
selectAll();
}
});
this.setHorizontalAlignment(JTextField.CENTER);
}
public MyJTextField() {}
public MyJTextField(String text) {super(text);}
public MyJTextField(int columns) {super(columns);}
public MyJTextField(String text, int columns) {super(text, columns);}
}

How can I make the DocumentListener compare after every line and not only once the program is run?

I started doing the assignment number #3 by just adding a basic code to understand how it works. But I can’t get out of this problem. I just added an “if” so that if the input text is equal to “hr”, then the turtle would move 2 squares to the right every time. But when I run the code, it is as if it only checks the first characters. If the first two characters are “hr” then it marks a point, but if not, it never again checks the input. So for example if I write:
re
Fd
hr
It never marks the point even though “hr” is there. What can I do so that the TurtleRenderer reads the line every time a \n is inserted and not only once the code is run?
My code:
package turtle;
public class BoardMaker {
private static int MAX = 100;
private boolean[][] board = new boolean[MAX][MAX];
int previousX = 0;
int previousY = 0;
public boolean[][] makeBoardFrom(String description) {
if(description.contentEquals("hr")){
previousX+=2;
board[previousX][previousY]=true;
}
return board;
}
public boolean[][] initialBoard() {
for(int i=0;i<MAX;i++)
{
for(int j=0;j<MAX;j++)
board[i][j]=false;
}
return board;
}
}
The TurtleRenderer class:
package turtle;
public class TurtleRenderer extends Panel implements DocumentListener {
private static final long serialVersionUID = 1;
static final Dimension WINDOW_SIZE = new Dimension(1150, 1150);
boolean [][] board;
final BoardMaker boardMaker;
public TurtleRenderer() {
boardMaker = new BoardMaker();
board = boardMaker.initialBoard();
}
static public void main(String args[]) throws Exception {
JFrame frame = new JFrame("Display image");
JPanel panel = new JPanel();
TurtleRenderer image = new TurtleRenderer();
image.setPreferredSize(WINDOW_SIZE);
JScrollPane textArea = makeTextArea(image);
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
panel.add(image);
buildRightPanel(panel, textArea);
frame.setSize(WINDOW_SIZE);
frame.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent we){
System.exit(0);
}});
frame.getContentPane().add(panel);
frame.setVisible(true);
}
static void buildRightPanel(JPanel panel,JComponent textArea) {
JLabel label = new JLabel("Your program:");
label.setPreferredSize(new Dimension(150, 20));
JPanel right = new JPanel();
textArea.setPreferredSize(new Dimension(150,500));
right.setLayout(new BoxLayout(right, BoxLayout.Y_AXIS));
right.add(label);
right.add(textArea);
panel.add(right);
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.white);
g.fillRect(0, 0, WINDOW_SIZE.width, WINDOW_SIZE.width);
if(board == null)
return;
g2d.setColor(Color.red);
for(int i=0;i<board.length;i++) {
for(int j=0;j<board.length;j++) {
if(board[i][j])
g2d.fillRect(9*i+1, 9*j+1, 6, 6);
}
}
}
static JScrollPane makeTextArea(TurtleRenderer image) {
JTextArea textArea = new JTextArea();
textArea.getDocument().addDocumentListener(image);
textArea.setVisible(true);
JScrollPane areaScrollPane = new JScrollPane(textArea);
areaScrollPane.setVerticalScrollBarPolicy(
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
areaScrollPane.setBorder(BorderFactory.createLineBorder(Color.black));
return areaScrollPane;
}
#Override
public void insertUpdate(DocumentEvent e) {
changed(e);
}
#Override
public void removeUpdate(DocumentEvent e) {
changed(e);
}
#Override
public void changedUpdate(DocumentEvent e) {
changed(e);
}
void changed(DocumentEvent de) {
String description;
Document document = de.getDocument();
try {
description = document.getText(0, document.getLength());
} catch (BadLocationException e) {
throw new RuntimeException(e);
}
try {
board = boardMaker.makeBoardFrom(description);
} catch(ParserException pe) {
board = null;
}
this.repaint();
}
}
Your problem is that you're currently testing if the whole text held by the JTextArea holds "hr". This may be true if hr is the only text in the JTextArea, but once more text is added, this will always be false. What you need to check is if the last line is "hr".
Since this is homework, I won't post a solution, but a pseudo-code logic solution for your DocumentListener could be:
try
get the text String from the document
get the last char from this String
if this last Char == carriage return which is (char)10
split the text into lines using the carriage return as the split's delimiter
get the last line held by this array and check it
if it is hr do something
end if last char == carriage return
end try
catch for BadLocationException
From the Javadocs,
public boolean contentEquals(CharSequence cs)
Compares this string to the specified CharSequence.
The result is true if and only if this String represents the same sequence of char values as the specified sequence.
public boolean contains(CharSequence s)
Returns true if and only if this string contains the specified sequence of char values.
Thus String.contentEquals will function more of a type of String.equals method. There are some differences though.
As with this problem you would require String.contains method to check whether the text contains the String "hr"
One more advice with regards to code efficiency :
You don't have to perform any action in the changedUpdate(DocumentEvent e) method within the DocumentListener. This method is only called when an attribute or a set of attributes has changed, i.e. the style of the document has changed which is not possible in a JTextArea as it does not support styled text.
I hope I have understood your problem correctly.
First, as in a previous comment, the method makeBoardFrom will every time receive
the entire program. If is up to you to split that program into individual commands, and then execute each command. You should not attempt to change the TurtleRenderer class to behave differently.
Second, if you want to move the turtle to the left by two squares you have to mark both
squares as visited, not just the destination square. Right now in your solution, by only using previousX+=2; you only mark the destination square as visited.
Third, in the initialBoard method you also have to actually mark the initial square of the turtle with true. In your case that will be the square at position (0, 0).

Maintaing JTextArea scroll position

I have a JScrollPane with a JTextArea set as its view port.
I update the (multi line) text shown on the JTextArea continously about once a second. Each time the text updates, JScrollPane goes all the way to the bottom of the text.
Instead, I'd like to figure out the line number that is currently shown as the first line in the original text, and have that line be the first line shown when the text has been updated (or if the new text doesn't have that many lines, then scroll all the way to the bottom).
My first attempt of doing this was to get the current caret position, figure the line based on that, and then set the text area to show that line:
int currentPos = textArea.getCaretPosition();
int currentLine = 0;
try {
for(int i = 0; i < textArea.getLineCount(); i++) {
if((currentPos >= textArea.getLineStartOffset(i)) && (currentPos < gameStateTextArea.getLineEndOffset(i))) {
currentLine = i;
break;
}
}
} catch(Exception e) { }
textArea.setText(text);
int newLine = Math.min(currentLine, textArea.getLineCount());
int newOffset = 0;
try {
newOffset = textArea.getLineStartOffset(newLine);
} catch(Exception e) { }
textArea.setCaretPosition(newOffset);
This was almost acceptable for my needs, but requires the user to click inside the text area to change the caret position, so that the scrolling will maintain state (which isn't nice).
How would I do this using the (vertical) scroll position instead ?
I encountered the same problem and found that this answer includes a nice solution that works in this case:
DefaultCaret caret = (DefaultCaret) jTextArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
This is pieced together, untested, from the API documentation:
use getViewport() on your JScrollPane to get a hold of the viewport.
use Viewport.getViewPosition() to get the top-left coordinates. These are absolute, not a percentage of scrolled text.
use Viewport.addChangeListener() to be notified when the top-left position changes (among other things). You may want to create a mechanism to distinguish user changes from changes your program makes, of course.
use Viewport.setViewPosition() to set the top-left position to where it was before the disturbance.
Update:
To stop JTextArea from scrolling, you may want to override its getScrollableTracksViewport{Height|Width}() methods to return false.
Update 2:
The following code does what you want. It's amazing how much trouble I had to go to to get it to work:
apparently the setViewPosition has to be postponed using invokeLater because if it's done too early the text update will come after it and nullify its effect.
also, for some weird reason perhaps having to do with concurrency, I had to pass the correct value to my Runnable class in its constructor. I had been using the "global" instance of orig and that kept setting my position to 0,0.
public class Sami extends JFrame implements ActionListener {
public static void main(String[] args) {
(new Sami()).setVisible(true);
}
private JTextArea textArea;
private JScrollPane scrollPane;
private JButton moreTextButton = new JButton("More text!");
private StringBuffer text = new StringBuffer("0 Silly random text.\n");
private Point orig = new Point(0, 0);
public Sami() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
this.textArea = new JTextArea() {
#Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
#Override
public boolean getScrollableTracksViewportWidth() {
return false;
}
};
this.scrollPane = new JScrollPane(this.textArea);
getContentPane().add(this.scrollPane, BorderLayout.CENTER);
this.moreTextButton.addActionListener(this);
getContentPane().add(this.moreTextButton, BorderLayout.SOUTH);
setSize(400, 300);
}
#Override
public void actionPerformed(ActionEvent arg0) {
int lineCount = this.text.toString().split("[\\r\\n]").length;
this.text.append(lineCount + "The quick brown fox jumped over the lazy dog.\n");
Point orig = this.scrollPane.getViewport().getViewPosition();
// System.out.println("Orig: " + orig);
this.textArea.setText(text.toString());
SwingUtilities.invokeLater(new LaterUpdater(orig));
}
class LaterUpdater implements Runnable {
private Point o;
public LaterUpdater(Point o) {
this.o = o;
}
public void run() {
// System.out.println("Set to: " + o);
Sami.this.scrollPane.getViewport().setViewPosition(o);
}
}
}

Categories

Resources