Java JTextPane add emty space to JScrollPane

I have A JTextPane

inside a JScrollPane

. What I'm trying to achieve is to add white space below JTextPane

to allow the user to scroll further in the text area so that the last line (which will be visible at the bottom of the frame) can be scrolled up to the position of the first line. It's hard to explain, so there are some photos here: enter image description here

My attempt to solve this was to add JPanel

from Borderlayout.SOUTH

to JTextPane

. And updatesetPreferredSize(0, frameHeight - (const+ fontHeight))

const = A constant value I got from experimenting

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

      

This works great until you change the font size ... Basically it's very hacky and doesn't seem to be very reliable.

The question is : is there a built-in method for adding this functionality, and if not, how can I improve the existing solution for more stability?

EDIT ----------------- Here's 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();
}
}

      

+3


source to share


2 answers


You can set the JTextPane field 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 want this method to be called whenever the JTextPane is resized, when it becomes displayable, and when its text changes. For good measure, I would also call this if the setPage method inherited by the JTextPane 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();
        }
    });
}

      

+3


source


Good for one, you could use a different layout manager. For example, with GridBagLayout

you can set the padding between components and between the border and components. I cannot confirm that this works.



0


source







All Articles