I'm creating a program in which user can record and analyze the start time and end time of an activity. I think it is having an autoresize problem: at the start of the program, the table shrinks weirdly. But when I click on any cell, it becomes normal.
Below is my code
import java.awt.*;
import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.swing.table.*;
import java.awt.event.*;
public class TestTimeLog {
private static int COLUMN_NUM = 4;
private int rowNum = 11;
private JTable table;
private JPanel tablePanel;
public static int FRAME_WIDTH = 700;
public static int FRAME_HEIGHT = 800;
/*
* Panel that holds the table
*/
public JPanel TimeLogPanel() {
JPanel timeLogPanel = new JPanel();
// Create border layout with horizontal gap and vertical gap between components
timeLogPanel.setLayout(new BorderLayout(20, 20));
// Set border with parameters of top, left, bottom and right spacing
timeLogPanel.setBorder(BorderFactory.createEmptyBorder(20, 40, 40, 20));
timeLogPanel.add(tablePanel, BorderLayout.CENTER);
// Create table panel and set layout
tablePanel = new JPanel();
tablePanel.setLayout(new BorderLayout());
// Add a component listener to be able to calculate the panel size, thus
// calculate width and height of column
tablePanel.addComponentListener(new ComponentListener() {
// Override component resized method and change cell width and height
#Override
public void componentResized(ComponentEvent e) {
if (tablePanel.getHeight() / rowNum >= 1) {
table.setRowHeight(tablePanel.getHeight() / rowNum);
}
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.getColumnModel().getColumn(0).setPreferredWidth(tablePanel.getWidth() / 5);
table.getColumnModel().getColumn(1).setPreferredWidth(tablePanel.getWidth() / 5);
table.getColumnModel().getColumn(2).setPreferredWidth(tablePanel.getWidth() / 2);
table.getColumnModel().getColumn(3).setPreferredWidth(tablePanel.getWidth() / 10);
}
// Below methods are not used but must be there, as a requirement when adding a
// listener
#Override
public void componentMoved(ComponentEvent e) {
}
#Override
public void componentShown(ComponentEvent e) {
}
#Override
public void componentHidden(ComponentEvent e) {
}
});
// Create a new table with a table model (defined in a different method below)
table = new JTable(tableModel());
// Change appearance
table.setShowGrid(true);
table.setGridColor(Color.BLACK);
table.setSelectionBackground(Color.WHITE);
table.setSelectionForeground(Color.BLACK);
// Set a raised border
table.setBorder(new EtchedBorder(EtchedBorder.RAISED));
// Add table to JScrollPane which is added to table panel
JScrollPane scrollPane = new JScrollPane(table);
tablePanel.add(scrollPane);
return timeLogPanel;
}
/*
* This method overrides a table model so that it shows desired column names
*/
public DefaultTableModel tableModel() {
DefaultTableModel model = new DefaultTableModel(rowNum, COLUMN_NUM) {
private static final long serialVersionUID = 1L;
String[] columnNames = { "Start Time", "End Time", "Activity", "Priority" };
#Override
public String getColumnName(int index) {
return columnNames[index];
}
};
return model;
}
/*
* This main method creates a JFrame that holds the JPanel
*/
public void main(String[] args) {
// Create a new frame
JFrame guiFrame = new JFrame("Time Log Program");
// Set size of the frame
guiFrame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
// Add the panel
guiFrame.add(TimeLogPanel());
// Exit normally on closing the window
guiFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Show the frame
guiFrame.setVisible(true);
}
}
Somewhere in the code, I did turn off auto resize mode. But the weird display still happens, and I've go not idea how to fix it.
I appreciate any suggestions. Thanks!
EDIT: Also, half of the time I run the program, it will show up perfectly right (a normal table), and half of other time a shrank table appears. It's so weird.
Related
I'm trying to create a small program, that I want to change the background slowly while scrolling the scrollbar (JScrollPane). It's like onscroll function in Javascript.
I created the scroll pane and added to it a frame then put some components in it, what I want now, is when the user scrolls, the BG of the scroll pane changes slowly from black to white, when it reaches the bounds of the frame:
This how I created the frame and scroll pane:
public AdminFrame() {
setBounds(20, 20, 1300, 700);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(new BorderLayout(0, 0));
JPanel contentPane = new JPanel();
contentPane.setForeground(Color.WHITE);
contentPane.setFont(new Font("Tahoma", Font.BOLD, 11));
contentPane.setBounds(120, 50, 692, 7);
contentPane.setPreferredSize(new Dimension(1300, 1500));
getContentPane().add(contentPane, BorderLayout.CENTER);
contentPane.setLayout(null);
JScrollPane scrollPane=new JScrollPane(contentPane,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setBackground(BgColor);
scrollPane.getVerticalScrollBar().setUnitIncrement(25);
}
I tested many ideas, also many block of solutions that I found in the web but nothing works.
Like:
scrollPane.getViewport().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
// TODO Auto-generated method stub
contentPane.setBackground(Color.white);
System.out.println("scorled");
}
} )
Or:
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// TODO Auto-generated method stub
scrollPane.setBackground(new Color(BgColor.getRed()-10,BgColor.getBlue()-10,BgColor.getGreen()-10)); //change the background color;
}
});
Introduction
Your "simple" project is not simple in Java. Java is not the same language as JavaScript. Simple things in JavaScript can be incredibly hard to duplicate in Java.
Here's a GUI I put together. This is the initial state.
This is the state with the vertical scroll bar about halfway down.
This is the state with the vertical scroll bar all the way down.
Explanation
Oracle has a nifty tutorial, Creating a GUI With JFC/Swing that will take you through the many many steps to create a Swing GUI. Skip the Netbeans section.
I created a background JPanel. I placed the background JPanel inside a JScrollPane. I placed the JScrollPane inside the main JPanel. I placed the main JPanel inside a JFrame.
I started the Swing application with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components will be created and executed on the Event Dispatch Thread.
I used Swing layout managers to layout the Swing components.
Code
Here's the complete runnable code. I made all the classes inner classes so I could post the code as one block.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class BackgroundColorChangeGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new BackgroundColorChangeGUI());
}
private final BackgroundColorChangeModel model;
private BackgroundPanel backgroundPanel;
public BackgroundColorChangeGUI() {
this.model = new BackgroundColorChangeModel();
}
#Override
public void run() {
JFrame frame = new JFrame("Color Change");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createMainPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createMainPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
backgroundPanel = new BackgroundPanel();
Dimension d = backgroundPanel.getPreferredSize();
int displayHeight = 400;
panel.setPreferredSize(new Dimension(d.width + 50, displayHeight));
JScrollPane scrollPane = new JScrollPane(backgroundPanel);
JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
scrollBar.setMaximum(d.height - displayHeight + 13);
scrollBar.setUnitIncrement(1);
model.setMinimumValue(scrollBar.getMinimum());
model.setMaximumValue(scrollBar.getMaximum());
scrollBar.addAdjustmentListener(new ScrollListener());
panel.add(scrollPane);
return panel;
}
public class BackgroundPanel extends JPanel {
private static final long serialVersionUID = 1L;
public BackgroundPanel() {
this.setPreferredSize(new Dimension(300, 5000));
setBackgroundColor(Color.BLACK);
}
public void setBackgroundColor(Color backgroundColor) {
this.setBackground(backgroundColor);
}
}
public class ScrollListener implements AdjustmentListener {
#Override
public void adjustmentValueChanged(AdjustmentEvent event) {
// System.out.println(event.getValue());
Color color = createBackgroundColor(event.getValue());
backgroundPanel.setBackgroundColor(color);
}
private Color createBackgroundColor(int value) {
// Black is 0, 0, 0; white is 255, 255, 255
int range = model.getMaximumValue() - model.getMinimumValue();
int colorValue = value * 255 / range;
return new Color(colorValue, colorValue, colorValue);
}
}
public class BackgroundColorChangeModel {
private int minimumValue;
private int maximumValue;
public int getMinimumValue() {
return minimumValue;
}
public void setMinimumValue(int minimumValue) {
this.minimumValue = minimumValue;
}
public int getMaximumValue() {
return maximumValue;
}
public void setMaximumValue(int maximumValue) {
this.maximumValue = maximumValue;
}
}
}
You should update the color for each event
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent evt) {
int x = evt.getValue() % 255;
contentPane.setBackground(new Color(x, x, x));
System.out.println("scorled");
}
});
Current code
ThreeColorButton class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ThreeColorButton {
private static CompositeIcon icons = new CompositeIcon();
private static JPanel panel = new JPanel();
private static JFrame frame = new JFrame();
private static JLabel label = new JLabel();
public static void main(String[] args) {
//create rgb buttons
JButton redButton = new JButton("Red");
JButton greenButton = new JButton("Green");
JButton blueButton = new JButton("Blue");
//add rgb buttons to panel
panel.add(redButton);
panel.add(greenButton);
panel.add(blueButton);
//add action listeners to buttons
redButton.addActionListener(buttonListener(40, Color.red));
greenButton.addActionListener(buttonListener(40, Color.green));
blueButton.addActionListener(buttonListener(40, Color.blue));
frame.setLayout(new BorderLayout());
frame.add(panel, BorderLayout.NORTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
private static ActionListener buttonListener(final int size,final Color color) {
return new ActionListener() {
public void actionPerformed(ActionEvent event) {
SquareIcon icon = new SquareIcon(size, color);
icons.addIcon(icon);
label.setIcon(icons);
frame.add(label, BorderLayout.SOUTH);
frame.repaint();
frame.pack();
}
};
}
}
CompositeIcon code
import javax.swing.*;
import java.awt.*;
import java.util.*;
public class CompositeIcon implements Icon{
private ArrayList<Icon> icons;
private int size;
public CompositeIcon() {
icons = new ArrayList<Icon>();
}
public void paintIcon(Component c, Graphics g, int x, int y) {
int position = x;
for(Icon z : icons) {
z.paintIcon(c,g,position,y);
position = position + z.getIconWidth();
}
}
public int getIconHeight() {
return size;
}
public int getIconWidth() {
int total = 0;
for(Icon z : icons) {
total = total + z.getIconWidth();
}
return total;
}
public void addIcon(Icon z) {
icons.add(z);
}
}
SquareIcon class is only a simple little class that creates a square of a single color with a given size.
My question is, in my ThreeColorButton class, when I run it, it doesn't show any icons when I press either of the RGB buttons. However, in the buttonListener method, if I set label.setIcons(icons) to label.setIcons(icon), it shows a single square and it doesnt place it side by side.
I can't seem to figure out whats causing this behavior. Is there a problem with displaying an array of icons using JLabel?
There is nothing to paint in the label since the height of your Icon is 0, since you never set the size.
I would suggest code something like:
#Override
public int getIconHeight()
{
int size = 0;
for(Icon z : icons)
{
size = Math.max(size, z.getIconHeight());
}
return size;
}
You may want to check out Compound Icon. Similar to your class but it has more features. It supports horizontal/vertical/stacked icon and icon alignment options.
It doesn't support dynamically adding Icons so you would need to change that. I might looking adding that feature myself (when I get time) :)
I can't seem to figure out whats causing this behavior. Is there a problem with displaying an array of icons using JLabel?
Yes, as #mKorbel mentioned, there is a problem. A JLabel can only display a single icon. If you want to display n icons, you will need n JLabel instances.
I'm trying to make a kitchen display system using a FlowLayout and I'm trying to figure out a way to add another panel on the 2nd row when the first row is already full. The width of the GUI will change according to user preference. When wider, it should show more of the components per row.
Approach - Variable width with WrapLayout
The GridLayout solution presumes the GUI requires 6 components per row.
For as many cols as needed to fill the width, & then show the components in as many rows as required, look to WrapLayout.
Approach - Variable width with JList
A JList can also be used here, since it seems all the components consist of a single (GUI) object.
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.border.*;
public class ListComponents {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
String[] listData = {
"Component 1", "Component 2", "Component 3",};
final DefaultListModel<String> model
= new DefaultListModel<String>();
for (String datum : listData) {
model.addElement(datum);
}
JList list = new JList(model);
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
list.setVisibleRowCount(-1);
list.setCellRenderer(new ObjectCellRenderer());
Action addAction = new AbstractAction("Add New") {
#Override
public void actionPerformed(ActionEvent e) {
model.addElement("New Component");
}
};
JButton addNew = new JButton(addAction);
JPanel ui = new JPanel(new BorderLayout(3, 3));
ui.setBorder(new EmptyBorder(4, 4, 4, 4));
ui.add(new JScrollPane(list), BorderLayout.CENTER);
ui.add(addNew, BorderLayout.PAGE_START);
JFrame f = new JFrame("Component List");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setContentPane(ui);
f.pack();
f.setLocationByPlatform(true);
f.setVisible(true);
}
};
SwingUtilities.invokeLater(r);
}
}
class ObjectCellRenderer extends DefaultListCellRenderer {
Border border = new EmptyBorder(20, 5, 20, 5);
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(
list, value, index, isSelected, cellHasFocus);
label.setBorder(border);
return label;
}
}
Approach - Fixed width with GridLayout
Using a GridLayout, when we know we always want a fixed number per row, regardless of width.
JPanel mainPanel = new JPanel(new GridLayout(0,6));
See new GridLayout(rows,cols):
Creates a grid layout with the specified number of rows and columns. All components in the layout are given equal size.
One, but not both, of rows and cols can be zero, which means that any number of objects can be placed in a row or in a column.
See this code for an example using a GridLayout(0,2) for the labels beneath Add Another Label
You can use a FlowLayout. From the tutorial How to Use FlowLayout:
The FlowLayout class puts components in a row, sized at their preferred size. If the horizontal space in the container is too small to put all the components in one row, the FlowLayout class uses multiple rows. If the container is wider than necessary for a row of components, the row is, by default, centered horizontally within the container.
EDIT If you want your layout to scroll vertically when there are too many rows, you can use a JScrollPane with horizontal scrolling disabled. You can do that with:
js.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
(where js is the JScrollPane that contains your FlowLayout panel).
EDIT 2 Well, the above isn't enough. You also need to set a viewport view on the JScrollPane that will track the JScrollPane width. Here's a class that does this (taken from here):
static class MyPanel extends JPanel implements Scrollable{
#Override
public Dimension getPreferredScrollableViewportSize() {
return this.getPreferredSize();
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation, int direction) {
return 50;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction) {
return 80;
}
/*
* (non-Javadoc)
* #see javax.swing.Scrollable#getScrollableTracksViewportWidth()
*/
#Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
#Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
You would use this by adding your current FlowLayout layout to an instance of MyPanel (instead of directly to the JScrollPane) and then calling
js.setViewportView(myPanelInstance);
I'm trying to resize a window dynamically using a Timer object, but not succeeding... I set the preferred size of the panel in the constructor, which sets the size of the window nicely, though only once. The preferred size changes after the program is initialized, but the window size stays the same. Why? Because the constructor is initialized only once and therefore isn't affected by the size change? If so, how could I get around this to resize the window in real-time?
I know this won't solve the problem in the exercise given in the beginning comments, so please ignore that :-)
/*
* Exercise 18.15
*
* "(Enlarge and shrink an image) Write an applet that will display a sequence of
* image files in different sizes. Initially, the viewing area for this image has
* a width of 300 and a height of 300. Your program should continuously shrink the
* viewing area by 1 in width and 1 in height until it reaches a width of 50 and
* a height of 50. At that point, the viewing area should continuously enlarge by
* 1 in width and 1 in height until it reaches a width of 300 and a height of 300.
* The viewing area should shrink and enlarge (alternately) to create animation
* for the single image."
*
* Created: 2014.01.07
*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Ex_18_15 extends JApplet {
// Main method
public static void main(String[] args) {
JFrame frame = new JFrame();
Ex_18_15 applet = new Ex_18_15();
applet.isStandalone = true;
frame.add(applet);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
// Data fields
private boolean isStandalone = false;
private Image image = new ImageIcon("greenguy.png").getImage();
private int xCoordinate = 360;
private int yCoordinate = 300;
private Timer timer = new Timer(20, new TimerListener());
private DrawPanel panel = new DrawPanel();
// Constructor
public Ex_18_15() {
panel.setPreferredSize(new Dimension(xCoordinate, yCoordinate));
add(panel);
timer.start();
}
class DrawPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, this);
}
}
class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if(yCoordinate <= 50) {
yCoordinate++;
xCoordinate++;
}
else if(yCoordinate >= 300) {
yCoordinate--;
xCoordinate--;
}
panel.setPreferredSize(new Dimension(xCoordinate, yCoordinate));
repaint();
}
}
}
You need to re-pack your JFrame to resize it. For instance at the end of your ActionListener:
Window win = SwingUtilities.getWindowAncestor(panel);
win.pack();
A question for you though: Why in heaven's name is your class extending JApplet and not JPanel? Or if it needs to be an applet, why are you stuffing it into a JFrame?
Edit
Regarding your comment:
Wouldn't it usually be extending JFrame not JPanel? I'm stuffing it into a JFrame to allow it to run as an application as well as an applet. That's how 'Introduction to Java Programming' tells me how to do it :p Adding your code at the end of the actionPerformed method didn't do anything for me ;o
Most of your GUI code should be geared towards creating JPanels, not JFrames or JApplets. You can then place your JPanels where needed and desired without difficulty. Your book has serious issues and should not be trusted if it is telling you this.
Edit 2
Works for me:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class ShrinkingGui extends JPanel {
private static final int INIT_W = 400;
private static final int INIT_H = INIT_W;
private static final int TIMER_DELAY = 20;
private int prefW = INIT_W;
private int prefH = INIT_H;
public ShrinkingGui() {
new Timer(TIMER_DELAY, new TimerListener()).start();;
}
public Dimension getPreferredSize() {
return new Dimension(prefW, prefH);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (prefW > 0 && prefH > 0) {
prefW--;
prefH--;
Window win = SwingUtilities.getWindowAncestor(ShrinkingGui.this);
win.pack();
} else {
((Timer)e.getSource()).stop();
}
}
}
private static void createAndShowGUI() {
ShrinkingGui paintEg = new ShrinkingGui();
JFrame frame = new JFrame("Shrinking Gui");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(paintEg);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
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);