I'd like to add a non-scrolling element, always visible element (a "pinned" element) in a JScrollPane of a Swing GUI. Taking this example code:
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
JEditorPane editorPane = new JEditorPane();
editorPane.setText("""
d
d
d
d
d
d
d
d
d
d
d
d
d
d
d
d
d
d
d
d
d
""");
JScrollPane scroll = new JScrollPane(editorPane);
editorPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
editorPane.add(new JLabel("this shouldn't scroll!"));
frame.getContentPane().add(scroll);
frame.setSize(600, 400);
frame.setVisible(true);
}
}
I need a way to:
I've been able to make an awful implementation of this using swing's glass pane, but it means a lot more manual updating for me, and position always being the same, no matter how the swing panel is resized.
Thank you for any help!
As a personal preference, I'd probably use a compound layout, similar to this example as it gives you more control over the the component being rendered (i.e, much easier to deal with live components like buttons) and its position.
You "could" make use of the JLayer
API, for example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLayer;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.plaf.LayerUI;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
public TestPane() throws IOException {
FixedTextLayerUI layerUI = new FixedTextLayerUI();
layerUI.setText("this shouldn't scroll!");
JTextArea ta = new JTextArea(20, 40);
StringBuilder sb = new StringBuilder(1024);
// Bring your own content ;)
try (BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/resources/StarWarsNewHope.txt")))) {
String text = null;
while ((text = br.readLine()) != null) {
sb.append(text);
sb.append(System.lineSeparator());
}
ta.setText(sb.toString());
}
JLayer<JComponent> layer = new JLayer<JComponent>(new JScrollPane(ta), layerUI);
setLayout(new BorderLayout());
add(layer);
}
}
public class FixedTextLayerUI extends LayerUI<JComponent> {
private String text;
public void setText(String text) {
this.text = text;
}
public String getText() {
return text;
}
@Override
public void paint(Graphics g, JComponent c) {
super.paint(g, c);
Graphics2D g2 = (Graphics2D) g.create();
FontMetrics fm = g2.getFontMetrics();
int stringWidth = fm.stringWidth(getText());
int x = c.getWidth() - stringWidth - 8;
int y = fm.getHeight() + fm.getAscent() + 8;
if (c instanceof JLayer) {
JLayer layer = (JLayer) c;
if (layer.getView() instanceof JScrollPane) {
JScrollPane scrollPane = (JScrollPane) layer.getView();
JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
if (scrollBar.isVisible()) {
x -= scrollBar.getWidth();
}
}
}
g2.drawString(text, x, y);
g2.dispose();
}
}
}
See How to Decorate Components with the JLayer Class for more details.