将 JScrollPane 的可见区域移动到特定位置

Moving the visible area of a JScrollPane to a specific position

我正在尝试使用 java 模拟调试光标的移动。我在将 JScrollPane 的可视区域设置到正确位置时遇到问题。

这是我想要实现的图片:

只有当我想跳转的行不可见时,我才想滚动。如果有帮助,可以使用 CodeDrowingPanel.NUMBER_OF_LINESCodeDrowingPanel.FONT_SIZE 来完成计算,这些线是使用这些常量绘制在面板上的。

如果一定要跳的话,我要跳的线应该在最下面。

我必须记住,可见区域取决于屏幕分辨率。应用程序已最大化,无法调整大小。

编辑:

public void setCursorToLine(int line, JScrollPane codeArea)
{
    if(line*CodeDrowingPanel.FONT_SIZE > this.getHeight()+43)
        this.cursorPosition = this.getHeight()+43;
    else
        this.cursorPosition = line * CodeDrowingPanel.FONT_SIZE;
    JViewport viewPort = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, codeArea);
    if (viewPort != null) 
    {
        Rectangle view = viewPort.getViewRect();
        view.y += line - previousLine;

        codeArea.scrollRectToVisible(view);
    }
    this.repaint();
}

这就是我现在尝试修改该行的方式。但它不起作用。我试着按照第一条评论中的第二个例子。不知道怎么用第二个评论的方法

这是一个简单的示例,使用 JListJScrollPanerowHeader 支持。

魔术基本上发生在这里...

int index = list.getSelectedIndex();
index++;
if (index >= list.getModel().getSize()) {
    index = 0;
}
list.setSelectedIndex(index);
Rectangle cellBounds = list.getCellBounds(index, index);
list.scrollRectToVisible(cellBounds);

基本上,我们要求视图计算由所选索引表示的Rectangle,并简单地要求组件滚动以便矩形可见

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JList list;

        public TestPane() {
            setLayout(new BorderLayout());
            DefaultListModel model = new DefaultListModel();
            try (BufferedReader br = new BufferedReader(new FileReader(new File("src/test/Test.java")))) {
                String text = null;
                while ((text = br.readLine()) != null) {
                    model.addElement(text);
                }
            } catch (IOException exp) {
                exp.printStackTrace();
            }
            list = new JList(model);
            list.setSelectedIndex(0);
            JScrollPane sp = new JScrollPane(list);
            sp.setRowHeaderView(new Header(list));

            add(sp);

            JButton next = new JButton("Next");
            next.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    int index = list.getSelectedIndex();
                    index++;
                    if (index >= list.getModel().getSize()) {
                        index = 0;
                    }
                    list.setSelectedIndex(index);
                    Rectangle cellBounds = list.getCellBounds(index, index);
                    list.scrollRectToVisible(cellBounds);
                }
            });

            add(next, BorderLayout.SOUTH);
        }

    }

    protected class Header extends JPanel {

        private JList list;

        public Header(JList list) {
            this.list = list;
            list.addListSelectionListener(new ListSelectionListener() {
                @Override
                public void valueChanged(ListSelectionEvent e) {
                    repaint();
                }
            });
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Container parent = list.getParent();
            if (parent instanceof JViewport) {
                JViewport viewport = (JViewport) parent;
                Graphics2D g2d = (Graphics2D) g.create();
                int selectedRow = list.getSelectedIndex();
                if (selectedRow >= 0) {
                    Rectangle cellBounds = list.getCellBounds(selectedRow, selectedRow);
                    cellBounds.y -= viewport.getViewPosition().y;
                    g2d.setColor(Color.RED);
                    g2d.fillRect(0, cellBounds.y, getWidth(), cellBounds.height);
                }
                g2d.dispose();
            }
        }

    }

}

我认为行号不应该是文本的一部分。例如,您有一个水平滚动条。如果向右滚动,您将丢失行号。

相反,您应该使用一行 header 来显示行号。

参见 Text Component Line Number。它包含一个 class 可以为您自定义绘制行号。您可以使用将此组件添加到行 header.

那个class里面的绘画代码会高亮显示当前行号。如果要添加箭头,则需要修改绘制代码。在 paintComponent(...) 方法中,您可以添加以下内容:

g.drawString(lineNumber, x, y);  

//  Code to paint an arrow

if (isCurrentLine(rowStartOffset))
{
    int height = fontMetrics.getAscent() - fontMetrics.getDescent();

    Polygon triangle = new Polygon();
    triangle.addPoint(borderGap, y);
    triangle.addPoint(borderGap, y - height);
    triangle.addPoint(borderGap + 10, y - height / 2);
    Graphics2D g2d = (Graphics2D)g.create();
    g2d.fill( triangle );
    g2d.dispose();
}

还有一个变化要做。由于我们现在正在绘制箭头,因此我们需要增加组件的宽度。因此,在 setPreferredWidth(...) 方法中,您需要进行以下更改:

//int preferredWidth = insets.left + insets.right + width;
int preferredWidth = insets.left + insets.right + width + 15;

I want to scroll only if the line I want to jump it is not visible.

这里有一些代码可以做到这一点:

public static void gotoStartOfLine(JTextComponent component, int line)
{
    Element root = component.getDocument().getDefaultRootElement();
    line = Math.max(line, 1);
    line = Math.min(line, root.getElementCount());
    int startOfLineOffset = root.getElement( line - 1 ).getStartOffset();
    component.setCaretPosition( startOfLineOffset );
}

我从 Text Utilities 中获取了上面的代码,它可能有其他感兴趣的方法(如果不是现在,将来)。

如果要在文本窗格中突出显示整行,也可以使用 Line Painter