My JTextArea contains thousands of lines but not all of them are visible at a time. I want to programmatically scroll to a specific row of the textArea so that the line is visible. I found that scrollPane has a method scrollRectToVisible but I am not successful with that. Can anyone suggest me how to accomplish the goal. A workable code snippet will be really helpful for me. Thanks.
scrollRectToVisible(...) should work. Make sure you invoke scrollRectToVisible(...) on the text area and not the scrollpane. If that doesn't work then I would guess you are not getting the proper Rectangle to scroll to. Post your SSCCE that demonstrates the problem.
Another approach is to use the gotoStartOfLine(...) method of the Text Utilities. You can also use the centerLineInScrollPane(...) method if you wish.
I guess you've answered this already. I was creating my SSCCE during this time, so I'll post it for others' benefit if not yours.
import java.awt.BorderLayout;
import java.awt.Rectangle;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.BadLocationException;
public class TestScrollRectToVisible extends JPanel {
private static final int MAX_LOOP = 10000;
private DefaultListModel listModel = new DefaultListModel();
private JTextArea textarea = new JTextArea(20, 30);
private JList jList = new JList(listModel);
JScrollPane textareaScrollPane = new JScrollPane(textarea);
public TestScrollRectToVisible() {
jList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
String text = jList.getSelectedValue().toString();
text += ": ";
String docText = textarea.getText();
int index = docText.indexOf(text);
if (index < 0) {
return;
}
try {
Rectangle rect = textarea.modelToView(index);
textarea.scrollRectToVisible(rect);
} catch (BadLocationException e1) {
e1.printStackTrace();
}
}
}
});
jList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
StringBuilder strBuilder = new StringBuilder();
for (int i = 0; i < MAX_LOOP; i++) {
String text = String.valueOf(i);
listModel.addElement(text);
strBuilder.append(text + ": abcdefghijklmnopqrstuvwxyz" + "\n");
}
textarea.setText(strBuilder.toString());
setLayout(new BorderLayout());
add(textareaScrollPane, BorderLayout.CENTER);
add(new JScrollPane(jList), BorderLayout.EAST);
}
private static void createAndShowUI() {
JFrame frame = new JFrame("TestScrollRectToVisible");
frame.getContentPane().add(new TestScrollRectToVisible());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
Related
I've made a JTextArea where I input different commands and separate them by newline "\n", and if there is an error in one of the lines then I write it in a console output. Here I made a very simple, and not the best solution to make this line indication, but it's a bit buggy.
How I made it
I've defined a textArea where I can type different information/commands, and if one of the commands/lines is invalid I write it in the console just to display something for now. I basically count the lines by splitting the textArea rows up by "\n" and then count which line the error occurs in, and the left consoleLineNum is using the amount of rows in textArea, to then make a string containing all the numbers of rows+"\n".
But here my question is, is this a good enough way? If so, why/how can I make it more robust? Or how can I make this indication with line numbers, in the left? It has to increase each time the user makes a new line in the textArea.
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setLayout(new BorderLayout(3,3));
/*----- Panels -----*/
JPanel panel1 = new JPanel();
JPanel panel2 = new JPanel();
JPanel panel3 = new JPanel();
//Add Components to this panel.
JScrollPane scrollPane = new JScrollPane(textArea);
JScrollPane scrollPaneOutput = new JScrollPane(consoleOutput);
textArea.setCaretPosition(textArea.getDocument().getLength());
consoleOutput.setEditable(false);
consoleLineNum.setEditable(false);
ButtonPanel_listener(buttonPanel);
textArea.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
consoleLineNum.setText("");
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= Integer.parseInt(String.valueOf(textArea.getText().split("\n").length)); i++) {
sb.append(i + " \n");
}
consoleLineNum.setText(sb.toString());
}
#Override
public void keyReleased(KeyEvent e) {
}
});
//Background
panel1.setBackground(Color.LIGHT_GRAY);
//Preferred size
panel1.setPreferredSize(new Dimension(100, 100));
scrollPane.setPreferredSize(new Dimension(200, 200));
panel2.setLayout(new BorderLayout());
panel2.add(scrollPane, BorderLayout.CENTER);
panel2.add(buttonPanel, BorderLayout.SOUTH);
panel2.add(consoleLineNum, BorderLayout.WEST);
consoleLineNum.setText(num);
panel3.setLayout(new BorderLayout());
panel3.add(drawCanvas, BorderLayout.CENTER);
panel1.setLayout(new BorderLayout());
panel1.add(scrollPaneOutput, BorderLayout.CENTER);
//textArea.addActionListener(e -> drawCanvas.drawCircle(250, 250, 200));
//Add contents to the window.
frame.add(panel2, BorderLayout.WEST);
frame.add(panel3, BorderLayout.CENTER);
frame.add(panel1, BorderLayout.SOUTH);
//Display the window.
frame.pack();
frame.setVisible(true);
}
Difference between your expected and actual results
When I hit newline/Enter, it doesn't show the number right away, only when I start typing.
Or
Here I want it to match where the user is, so if the user hit enter, and go to the next line, then the number matches and is shown right away.
If I delete all lines, except some, it still shows the numbers
Here I want it to wipe all the numbers and update it to match the amount of data in textArea.
Tried this, and it works almost as expected. The only problem is that when I delete lines, it's one behind.
#Override
public void keyPressed(KeyEvent e) {
numI = textArea.getLineCount();
if(e.getKeyCode() == KeyEvent.VK_BACK_SPACE && numI > 0) {
numI = numI - 1;
}
if(e.getKeyCode() == KeyEvent.VK_BACK_SPACE || e.getKeyCode() == KeyEvent.VK_ENTER){
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= numI; i++) {
sb.append(i+1 + " \n");
}
consoleLineNum.setText(sb.toString());
}
}
Use a DocumentListener to listen for changes in the text of the JTextArea. The API of JTextArea already provides a method that tells you how many lines it contains, namely getLineCount(). After placing the JTextArea in a JScrollPane, set a JList as the row header for the JScrollPane.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JViewport;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class CountLns implements DocumentListener, Runnable {
private JFrame frame;
private JList<Integer> lineNumbersList;
private JTextArea textArea;
public void changedUpdate(DocumentEvent docEvent) {
// Never called.
}
public void insertUpdate(DocumentEvent docEvent) {
handleDocEvent();
}
public void removeUpdate(DocumentEvent docEvent) {
handleDocEvent();
}
#Override
public void run() {
showGui();
}
private JScrollPane createTextArea() {
textArea = new JTextArea(20, 50);
textArea.getDocument().addDocumentListener(this);
JScrollPane scrollPane = new JScrollPane(textArea);
lineNumbersList = new JList<>(new Integer[]{1});
lineNumbersList.setBackground(Color.cyan);
lineNumbersList.setFont(textArea.getFont());
lineNumbersList.setFixedCellHeight(16);
JViewport rowHeader = new JViewport();
rowHeader.setView(lineNumbersList);
scrollPane.setRowHeader(rowHeader);
return scrollPane;
}
private void handleDocEvent() {
DefaultListModel<Integer> model = new DefaultListModel<>();
List<Integer> lineNumbers = IntStream.range(0, textArea.getLineCount())
.boxed()
.map(i -> i + 1)
.collect(Collectors.toList());
model.addAll(lineNumbers);
lineNumbersList.setModel(model);
}
private void showGui() {
frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(createTextArea(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
textArea.requestFocusInWindow();
frame.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new CountLns());
}
}
Whenever the contents of the JTextArea change, the DocumentListener counts the lines in the JTextArea and sets the JList model to contain exactly that number of elements. So if the JTextArea has 12 lines, the JList will contain all the numbers from 1 to 12.
Try doing it like this:
yourTextArea.addKeyListener(new KeyAdapter() {
#Override
public void keyTyped(KeyEvent e) {
// Returns true every time the user presses either backspace or enter.
if (e.getKeyChar() == '\n' || e.getKeyChar() == '\b') {
int lines;
// Checks if the text ends with a newline, if so,
// it adds 1 to the line count, so that would make your line
// appear even if the user just started a new line.
if (yourTextArea.getText().endsWith("\n")) {
lines = yourTextArea.getText().split("\n").length + 1;
} else {
lines = yourTextArea.getText().split("\n").length;
}
// Removes previous count.
linePanel.setText("");
// Appends a new line to the area for every line.
for (int i = 0; i < lines; i++) {
linePanel.append((i + 1) + "\n");
}
}
}
});
I did not write in Java for 1 year so sorry, if I messed up syntax
First of all, why won't you implement line wrap and line count increment by pressing Enter? It looks much more simple to me
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER) {
textArea.append("\n");
Integer lineCount = Integer.parseInt(consoleLineNum.getText());
Integer newLineNumber = new Integer(lineCount.intValue() + 1)
consoleLineNum.append("\n" + newLineNumber.toString())
}
}
However you still will not get actual number of lines, if some of them will be deleted. So you can also add trickier logic to your keyPressed() method
if(e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
// Well I actually googled this line for counting occurences
Integer linesCountActual = textArea.getText().split("\n", -1).length - 1);
Integer linesCountLeft = consoleLineNum.getText().split("\n", -1).length - 1);
if (linesCountActual > linesCountLeft) {
String oldText = consoleLineNum.getText();
String newText = oldText.substring(0, oldText.length() - 2);
consoleLineNum.setText(newText);
}
}
I'm trying to use the JButton count to count the number of characters entered into the JTextField t. I'm new to Java and GUIs but here's my code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUI1 extends Frame implements ActionListener{
TextField t;
Button count;
int a;
Choice choice;
public GUI1(){
this.t = new TextField("", 30);
this.count = new Button("count");
this.count.addActionListener(this);
JTextField x = new JTextField();
x.setEditable(false);
this.setTitle("Character count");
this.setLayout(new FlowLayout());
this.add(x);
this.add(t);
this.add(count);
this.pack();
this.setVisible(true);
}
public void actionPerformed(ActionEvent e) {
if(e.getSource()== this.count)
t.setText(choice.getSelectedItem()+ " " +a);
}
I'm also trying to enter the value in another uneditable JTextField x. Any help is appreciated.
Add this to your code
count.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
a = t.getText().length();
}
});
OR
You can use lambda expression like this
count.addActionListener(e -> a = t.getText().length());
For More
http://docs.oracle.com/javase/7/docs/api/java/awt/event/ActionListener.html
You need to add a listener
TextField t = new TextField();
Button b = new Button("Count");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int count = t.getText().length();
}
});
You can read more about here
http://www.tutorialspoint.com/awt/awt_button.htm
First of all, I recommend you not to use AWT elements, since it brings tons of problems and it has little to no support, instead you can try using Swing components which are a replacement/fix for AWT. You can read more about here. You might also want to read AWT vs Swing (Pros and Cons).
Now going into your problem:
You should avoid extending from JFrame, I might recommend you to create a new JFrame object instead. Here's the reason to why. That being said, you can also remove all your this.t and other calls with this.
I'm glad you're using a Layout Manager!
And now to count the number of characters on your JTextField and set text to your other JTextField you should use this code:
count.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int count = t.getText().length();
System.out.println(count);
x.setText(t.getText());
}
});
Also I fixed your code, I changed AWT elements to Swing ones and added number of cols to your second JTextField so it would appear.
So, here's a running example that I made from your code (And removed Choice choice line since you didn't posted that code):
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUI1 {
JTextField t;
JButton count;
int a;
JFrame frame;
public GUI1(){
frame = new JFrame();
t = new JTextField("", 15);
count = new JButton("count");
JTextField x = new JTextField("", 15);
x.setEditable(false);
count.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int count = t.getText().length();
System.out.println(count);
x.setText(t.getText());
}
});
frame.setTitle("Character count");
frame.setLayout(new FlowLayout());
frame.add(x);
frame.add(t);
frame.add(count);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
public static void main (String args[]) {
new GUI1();
}
}
When you click on a button you should
String str = t.getText(); // get string from jtextfield
To save the text from the texfield. Then you can use something like:
a = str..length(); // get length of string
x.setText(str + " " + a); //set it to the field
To set it to the JTextField.
I'm currently creating a soundboard like application where the person is going to click one of the items in my JList and its going to set the name of the next available text field as what was just clicked.
For example, my JList contains hello, testing1, testing2. If testing2 is clicked first I would like to put it into the first textfield, and if hello is clicked next I'd like to put it into the 2nd textfield and so on.
The program will have around 100 items in the JList by the time the app is done. I currently can not get this to work and have tried countless times.
Also there is a problem that when the top one and a different JList item is clicked the top one will display first. Not necessarily a problem if I can get the problem fully functional but it makes it feel a little wonky.
My code so far:
package com.leagueoflegends.soundboard;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
public class Soundboard implements ListSelectionListener {
static JList<Object> list;
String[] text = { "hello", "testing1", "testing2" };
Icon icon;
JLabel pictureLabel;
JPanel insidePanel;
JTextField inlineText;
JTextField field[] = new JTextField[6];
public Soundboard() {
JFrame f = new JFrame("soundboard!");
JPanel masterPanel = new JPanel(new BorderLayout());
//icon = new ImageIcon(getClass().getResource("Tray.png"));
//pictureLabel = new JLabel(icon);
list = new JList<Object>(text); // data has type Object[]
list.setSelectionModel(new DefaultListSelectionModel(){
public void setSelectionInterval(int index0, int index1){
if(super.isSelectedIndex(index0)){
super.removeSelectionInterval(index0,index1);
}else{
super.addSelectionInterval(index0,index1);
}
}
});
list.setLayoutOrientation(JList.VERTICAL_WRAP);
list.setVisibleRowCount(-1);
list.addListSelectionListener(this);
JScrollPane listScroller = new JScrollPane(list);
listScroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
listScroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
listScroller.setPreferredSize(new Dimension(100, 60));
// listScroller.setSize(new Dimension(250, 60));
JPanel smallPanel = new JPanel(new GridLayout(2, 3));
// smallPanel.setBorder(BorderFactory.createLineBorder(Color.red));
for (int i = 0; i <= 5; i++) {
insidePanel = new JPanel(new BorderLayout());
insidePanel.setBorder(BorderFactory.createLineBorder(Color.black));
field[i] = new JTextField();
field[i].setEditable(false);
field[i].setHorizontalAlignment(JTextField.CENTER);
insidePanel.add(field[i], BorderLayout.PAGE_START);
smallPanel.add(insidePanel);
}
masterPanel.add(smallPanel);
// masterPanel.add(pictureLabel, BorderLayout.PAGE_START);
masterPanel.add(listScroller, BorderLayout.WEST);
f.add(masterPanel);
f.pack();
f.setSize(1000, 800);
f.setMinimumSize(new Dimension(400, 350));
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
#Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting() == false) {
for (int i = 0; i < text.length; i++) {
if (list.getSelectedIndex() == i) {
field[0].setText(text[i]);
}
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException
| UnsupportedLookAndFeelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new Soundboard();
}
});
}
}
The general idea would be to;
Create an array or List of your JTextFields. Add these your UI.
Create a counter, indicating the current "empty" field
Use either a MouseListener or ListSelectionListener to identify when the user has selected something, extract the value from the list. Using the "current" counter, extract the JTextField from the List and set it's value accordingly, increment the counter
I want to open a text file in a frame using swing components, preferably with highlighting facility. I get the name of the text file in a text filed in the first frame and want to open the text file in the second frame.My code is
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class FirstGUI extends JFrame {
private JLabel label;
private JTextField textfield;
private JButton button;
public FirstGUI() {
setLayout(new FlowLayout());
label = new JLabel("Enter the file path:");
add(label);
textfield = new JTextField();
add(textfield);
button = new JButton("Open");
add(button);
AnyClass ob = new AnyClass();
button.addActionListener(ob);
}
public class AnyClass implements ActionListener {
public void actionPerformed(ActionEvent obj) {
//SecondGUI s =new SecondGUI();
//s.setVisible(true);
}
}
public static void main(String[] args) {
FirstGUI obj= new FirstGUI();
obj.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
obj.setSize(600,600);
obj.setLocation(100,100);
obj.setVisible(true);
}
}
What swing component should I use in my second frame to open a text file in it ? If possible, please provide the outline of the code !!
Extending on mKorbel and Dans answer:
Well you could use a JTextArea like so:
import java.awt.BorderLayout;
import java.awt.Color;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.text.*;
public class LineHighlightPainter {
String revisedText = "Hello, World! ";
String token = "Hello";
public static void main(String args[]) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
new LineHighlightPainter().createAndShowGUI();
}
});
} catch (InterruptedException | InvocationTargetException ex) {
ex.printStackTrace();
}
}
public void createAndShowGUI() {
JFrame frame = new JFrame("LineHighlightPainter demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTextArea area = new JTextArea(9, 45);
area.setLineWrap(true);
area.setWrapStyleWord(true);
area.setText(revisedText);
// Highlighting part of the text in the instance of JTextArea
// based on token.
highlight(area, token);
frame.getContentPane().add(new JScrollPane(area), BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
// Creates highlights around all occurrences of pattern in textComp
public void highlight(JTextComponent textComp, String pattern) {
// First remove all old highlights
removeHighlights(textComp);
try {
Highlighter hilite = textComp.getHighlighter();
Document doc = textComp.getDocument();
String text = doc.getText(0, doc.getLength());
int pos = 0;
// Search for pattern
while ((pos = text.indexOf(pattern, pos)) >= 0) {
// Create highlighter using private painter and apply around pattern
hilite.addHighlight(pos, pos + pattern.length(), myHighlightPainter);
pos += pattern.length();
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
// Removes only our private highlights
public void removeHighlights(JTextComponent textComp) {
Highlighter hilite = textComp.getHighlighter();
Highlighter.Highlight[] hilites = hilite.getHighlights();
for (int i = 0; i < hilites.length; i++) {
if (hilites[i].getPainter() instanceof MyHighlightPainter) {
hilite.removeHighlight(hilites[i]);
}
}
}
// An instance of the private subclass of the default highlight painter
Highlighter.HighlightPainter myHighlightPainter = new MyHighlightPainter(Color.red);
// A private subclass of the default highlight painter
class MyHighlightPainter
extends DefaultHighlighter.DefaultHighlightPainter {
public MyHighlightPainter(Color color) {
super(color);
}
}
}
Or alternatively use a JTextPane and text can be highlighted by:
1) Changing any style attributes of arbitrary text parts on the document level, something like:
SimpleAttributeSet sas = new SimpleAttributeSet();
StyleConstants.setForeground(sas, Color.YELLOW);
doc.setCharacterAttributes(start, length, sas, false);
2) Highlight via a Highlighter on the textPane level:
DefaultHighlighter.DefaultHighlightPainter highlightPainter = new DefaultHighlighter.DefaultHighlightPainter(Color.YELLOW);
textPane.getHighlighter().addHighlight(startPos, endPos,highlightPainter);
References:
Highlighting Text in java
JTextPane highlight text
The simplest choice would be a JTextArea.
Another better choice is a JEditorPane.
You can take a look at this text components tutorial for understanding them better and choosing the best you need.
have look at
JFileChooser
JTextComponent#read()
JtextArea text
fileInputStream myFIS;
objectInputStream myOIS(myFIS);
Data = myOIS.read();
text.setText(Data);
that should give you some kind of an idea where to go. Don't forget to set the file input stream up with a file location so it knows what file to open. Then the ObjectInputStream will take the data and save the information into a field called Data. Then set the textArea to use Data as the information to "Set" the textArea to display.
Note: ObjectInputStream is not the only input stream available to use. you will have to use the input stream that correlates to your file.
I have a Swing app with a large panel which is wrapped in a JScrollPane. Users normally move between the panel's subcomponents by tabbing, so when they tab to something out view, I want the scroll pane to autoscroll so the component with input focus is always visible.
I've tried using KeyboardFocusManager to listen for input focus changes, and then calling scrollRectToVisible.
Here's an SSCCE displaying my current strategy (just copy/paste and run!):
import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
public class FollowFocus {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final int ROWS = 100;
final JPanel content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
content.add(new JLabel(
"Thanks for helping out. Use tab to move around."));
for (int i = 0; i < ROWS; i++) {
JTextField field = new JTextField("" + i);
field.setName("field#" + i);
content.add(field);
}
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addPropertyChangeListener("focusOwner",
new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (!(evt.getNewValue() instanceof JComponent)) {
return;
}
JComponent focused = (JComponent) evt.getNewValue();
if (content.isAncestorOf(focused)) {
System.out.println("Scrolling to " + focused.getName());
focused.scrollRectToVisible(focused.getBounds());
}
}
});
JFrame window = new JFrame("Follow focus");
window.setContentPane(new JScrollPane(content));
window.setSize(200, 200);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
});
}
}
If you run this example, you'll notice it doesn't work very well. It does get the focus change notifications, but the call to scrollRectToVisible doesn't appear to have any effect. In my app (which is too complex to show here), scrollRectToVisible works about half the time when I tab into something outside of the viewport.
Is there an established way to solve this problem? If it makes any difference, the Swing app is built on Netbeans RCP (and most of our customers run Windows).
My comment to the other answer:
scrollRectToVisible on the component itself is the whole point of that
method ;-) It's passed up the hierarchy until a parent doing the
scroll is found
... except when the component itself handles it - as JTextField does: it's implemented to scroll horizontally to make the caret visible. The way out is to call the method on the field's parent.
Edit
just for clarity, the replaced line is
content.scrollRectToVisible(focused.getBounds());
you have to take Rectangle from JPanel and JViewPort too, then compare, for example
notice (against down-voting) for final and nice output required some work for positions in the JViewPort
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
//http://stackoverflow.com/questions/8245328/how-do-i-make-jscrollpane-scroll-to-follow-input-focus
public class FollowFocus {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final int ROWS = 100;
final JPanel content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
content.add(new JLabel(
"Thanks for helping out. Use tab to move around."));
for (int i = 0; i < ROWS; i++) {
JTextField field = new JTextField("" + i);
field.setName("field#" + i);
content.add(field);
}
final JScrollPane scroll = new JScrollPane(content);
KeyboardFocusManager.getCurrentKeyboardFocusManager().
addPropertyChangeListener("focusOwner", new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (!(evt.getNewValue() instanceof JComponent)) {
return;
}
JViewport viewport = (JViewport) content.getParent();
JComponent focused = (JComponent) evt.getNewValue();
if (content.isAncestorOf(focused)) {
System.out.println("Scrolling to " + focused.getName());
Rectangle rect = focused.getBounds();
Rectangle r2 = viewport.getVisibleRect();
content.scrollRectToVisible(new Rectangle(rect.x, rect.y, (int) r2.getWidth(), (int) r2.getHeight()));
}
}
});
JFrame window = new JFrame("Follow focus");
window.setContentPane(new JScrollPane(content));
window.setSize(200, 200);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
});
}
}
Here my short summary.
Add this to your Tools class:
public static void addOnEnter(Component c, Consumer<FocusEvent> onEnter) {
FocusListener fl = new FocusListener() {
#Override
public void focusGained(FocusEvent e) {
onEnter.accept(e);
}
#Override
public void focusLost(FocusEvent e) { }
};
c.addFocusListener(fl);
}
public static void scrollToFocus(FocusEvent e) {
((JComponent) e.getComponent().getParent()).scrollRectToVisible(
e.getComponent().getBounds());
}
and use it like this:
Tools.addOnEnter(component, Tools::scrollToFocus);
component can be JTextField, JButton, ...
One major issue in your code is:
focused.scrollRectToVisible(focused.getBounds());
You are calling scrollRectToVisible on the component itself! Presumably a typo.
Make your JScrollPane a final variable and call
scrollPane.getViewport().scrollRectToVisible(focused.getBounds());
Here jtextbox is the component you want to focus and jscrollpane is your scrollpane:
jScrollpane.getVerticalScrollBar().setValue(jtextbox.getLocation().x);