Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java JTextPane add emty space to JScrollPane

I have A JTextPane inside a JScrollPane. What I am trying to achieve is to add an empty space under the JTextPane to allow the user to scroll further down the text pane, so that the last line (Which would be visible at the bottom of the frame) can be scrolled up to the first line position. Hard to explain so here have some pics: enter image description here

My attempt to solve this was to add a JPanel with Borderlayout.SOUTH under the JTextPane. And update the setPreferredSize(0, frameHeight - (const+ fontHeight))

const = A constant value I got from experimenting

int fontHeight = textPane.getFontMetrics(textPane.getFont()).getHeight();

This works fine until you resize the font... Basically it is very hacky and doesn't seem to be very reliable.

Question: Is there a internal method to add this feature and if not how could I improve the existing solution for more stability?

EDIT----------------- Here the working code:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.text.BadLocationException;


public class Main extends JFrame{
    private JTextPane zNum, tField;
    private JPanel scrollPanel;
    private JScrollPane tScrollPane;
    private Font font = new Font("Source Code Pro", Font.PLAIN, 12);

public Main(){
    super("Test Editor");
    getContentPane().setLayout(new BorderLayout());

    zNum = new JTextPane();
    zNum.setText("1");
    zNum.setEditable(false);
    zNum.setBorder(new EmptyBorder(2, 6, 2, 6));
    zNum.setBackground(Color.GRAY);
    zNum.setFont(font);

    tField = new JTextPane();
    tField.setBorder(new EmptyBorder(2, 6, 2, 6));
    tField.setBackground(Color.LIGHT_GRAY);
    tField.setFont(font);

    scrollPanel = new JPanel();
    scrollPanel.setLayout(new BorderLayout());
    scrollPanel.add(zNum, BorderLayout.LINE_START);
    scrollPanel.add(tField, BorderLayout.CENTER);
    scrollPanel.setBackground(Color.LIGHT_GRAY);

    tScrollPane = new JScrollPane(scrollPanel);
    tScrollPane.setBorder(BorderFactory.createEmptyBorder());
    getContentPane().add(tScrollPane, BorderLayout.CENTER);

    setPreferredSize(new Dimension(300, 200));
    setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    setResizable(true);
    pack();
    setLocationRelativeTo(null);
    setVisible(true);

    // Resize
    this.addComponentListener(new ComponentAdapter() {
        public void componentResized(ComponentEvent event) {
            updateMargin(tField);
        }
    });
}

void updateMargin(JTextPane textPane) {
    JViewport viewport = (JViewport)
        SwingUtilities.getAncestorOfClass(JViewport.class, textPane);

    if (viewport != null) {
        int len = textPane.getDocument().getLength();
        try {
            Rectangle end = textPane.modelToView(len);
            if (end != null) {
                tField.setBorder(new EmptyBorder(0, 0, viewport.getHeight() - (end.height + 4), 0));
            }
        } catch (BadLocationException e) {
            throw new RuntimeException(e);
        }
    }
}

public static void main(String[] args){
    new Main();
}
}
like image 816
Haeri Avatar asked Aug 28 '26 08:08

Haeri


1 Answers

You can set the JTextPane's margin, based on the bounds of its enclosing JViewport and the last line of its document:

static void updateMargin(JTextPane textPane) {
    JViewport viewport = (JViewport)
        SwingUtilities.getAncestorOfClass(JViewport.class, textPane);

    if (viewport != null) {
        Insets margin = textPane.getMargin();

        int len = textPane.getDocument().getLength();
        try {
            Rectangle end = textPane.modelToView(len);
            if (end != null) {
                margin.bottom = viewport.getHeight() - end.height;
                textPane.setMargin(margin);
            }
        } catch (BadLocationException e) {
            throw new RuntimeException(e);
        }
    }
}

You'll want to have that method called whenever the JTextPane is resized, when it becomes displayable, and when its text is changed. For good measure, I would also call it if the JTextPane's inherited setPage method is called:

static void configureMargin(final JTextPane textPane) {
    textPane.addPropertyChangeListener("page", new PropertyChangeListener() {
        @Override
        public void propertyChange(PropertyChangeEvent event) {
            updateMargin(textPane);
        }
    });

    textPane.addHierarchyListener(new HierarchyListener() {
        @Override
        public void hierarchyChanged(HierarchyEvent event) {
            long flags = event.getChangeFlags();
            if ((flags & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
                updateMargin(textPane);
            }
        }
    });

    textPane.addComponentListener(new ComponentAdapter() {
        @Override
        public void componentResized(ComponentEvent event) {
            updateMargin(textPane);
        }
    });

    textPane.getDocument().addDocumentListener(new DocumentListener() {
        private void updateTextPane() {
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    updateMargin(textPane);
                }
            });
        }

        @Override
        public void changedUpdate(DocumentEvent event) {
            updateTextPane();
        }

        @Override
        public void insertUpdate(DocumentEvent event) {
            updateTextPane();
        }

        @Override
        public void removeUpdate(DocumentEvent event) {
            updateTextPane();
        }
    });
}
like image 91
VGR Avatar answered Aug 29 '26 21:08

VGR



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!