1

注:私はそれに対する答えが必要であり、そこにある答えには壊れたリンクがあるので、私はこの質問を再質問しています。

私は学校向けのプロジェクトの一環として簡単なチャットプログラムを作成しています。メッセージが一番下にある場合は、メッセージを自動的に追跡できるようにしたいのですが、メッセージが下にある場合は、もう一度上にスクロールする必要はありません。上の何かを見ています。現在、私のコードは何があっても一番下までスクロールしています。基本的なコードは以下のとおりですが、SSCCEに変換する方法がわかりません。よろしければお気軽にどうぞ。

JTextArea jta = new JTextArea(50, 50);
JScrollPane scroll = new JScrollPane(jta);
try {
    int iter = 0;
    while (true) { //Not a for loop because it should not end after x iterations
        Thread.sleep(500);
        jta.append("Test text #" + iter);
        iter++;
        //Check if they are at the bottom and if so scroll down to the new bottom.
    }
} catch (InterruptedException ex) {
    System.out.println("Error!");
}
4

3 に答える 3

3

これが最も良い方法かどうかはわかりませんが、ユーザーが上にスクロールするたびに下へのスクロールを停止するコードを次に示します。彼が一番下にスクロールして戻ると、自動スクロールが再開されます。

アイデアは、テキストエリアの高さと、スクロールペインの表示されている長方形の位置と高さを比較することによって、ユーザーが上に移動したかどうかを確認することです。一致する場合は、スクロールが一番下にあり、ユーザーが自動スクロールを望んでいることを意味します。それ以外の場合は、textareaが変更されるたびに、表示されている長方形を強制的に同じままにします。

小さなSSCCE

import java.awt.BorderLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class TestScrollbars {

    protected void initUI() {
        final JFrame frame = new JFrame();
        frame.setTitle(TestScrollbars.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        final JTextArea chat = new JTextArea(10, 40);
        chat.setLineWrap(true);
        chat.setEditable(false);
        chat.setWrapStyleWord(false);
        final JScrollPane scrollPane = new JScrollPane(chat);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        frame.setLayout(new BorderLayout());
        frame.add(scrollPane, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
        Timer t = new Timer(200, new ActionListener() {

            private int i = 1;

            @Override
            public void actionPerformed(ActionEvent e) {
                final Rectangle visibleRect = chat.getVisibleRect();
                boolean scroll = chat.getHeight() <= visibleRect.y + visibleRect.height;
                chat.append("Hello line " + i++ + "\n");
                if (!scroll) {
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            chat.scrollRectToVisible(visibleRect);
                        }
                    });
                }
            }
        });
        t.start();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new TestScrollbars().initUI();
            }
        });
    }

}
于 2013-02-26T15:59:43.307 に答える
2

編集:

次のコードを、JScrollPaneの任意のコンポーネントで機能するより柔軟なバージョンに置き換えました。チェックアウト:スマートスクロール

JTextAreaまたはJTextPaneを含むスクロールペインで使用できる再利用可能なコードを次に示します。

import java.awt.*;
import java.awt.event.*;
import java.util.Date;
import javax.swing.*;
import javax.swing.text.*;

public class ScrollControl implements AdjustmentListener
{
    private JScrollBar scrollBar;
    private JTextComponent textComponent;
    private int previousExtent = -1;

    public ScrollControl(JScrollPane scrollPane)
    {
        Component view = scrollPane.getViewport().getView();

        if (! (view instanceof JTextComponent))
            throw new IllegalArgumentException("Scrollpane must contain a JTextComponent");

        textComponent = (JTextComponent)view;

        scrollBar = scrollPane.getVerticalScrollBar();
        scrollBar.addAdjustmentListener( this );
    }

    @Override
    public void adjustmentValueChanged(final AdjustmentEvent e)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                checkScrollBar(e);
            }
        });
    }

    private void checkScrollBar(AdjustmentEvent e)
    {
        //  The scroll bar model contains information needed to determine the
        //  caret update policy.

        JScrollBar scrollBar = (JScrollBar)e.getSource();
        BoundedRangeModel model = scrollBar.getModel();
        int value = model.getValue();
        int extent = model.getExtent();
        int maximum = model.getMaximum();
        DefaultCaret caret = (DefaultCaret)textComponent.getCaret();

        //  When the size of the viewport changes there is no need to change the
        //  caret update policy.

        if (previousExtent != extent)
        {
            //  When the height of a scrollpane is decreased the scrollbar is
            //  moved up from the bottom for some reason. Reposition the
            //  scrollbar at the bottom

            if (extent < previousExtent
            &&  caret.getUpdatePolicy() == DefaultCaret.UPDATE_WHEN_ON_EDT)
            {
                scrollBar.setValue( maximum );
            }

            previousExtent = extent;
            return;
        }

        //  Text components will not scroll to the bottom of a scroll pane when
        //  a bottom inset is used. Therefore the location of the scrollbar,
        //  the height of the viewport, and the bottom inset value must be
        //  considered when determining if the scrollbar is at the bottom.

        int bottom = textComponent.getInsets().bottom;

        if (value + extent + bottom < maximum)
        {
            if (caret.getUpdatePolicy() != DefaultCaret.NEVER_UPDATE)
                caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
        }
        else
        {
            if (caret.getUpdatePolicy() != DefaultCaret.UPDATE_WHEN_ON_EDT)
            {
                caret.setDot(textComponent.getDocument().getLength());
                caret.setUpdatePolicy(DefaultCaret.UPDATE_WHEN_ON_EDT);
            }
        }
    }

    private static void createAndShowUI()
    {
        JPanel center = new JPanel( new GridLayout(1, 2) );
        String text = "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n";

        final JTextArea textArea = new JTextArea();
        textArea.setText( text );
        textArea.setEditable( false );
        center.add( createScrollPane( textArea ) );
        System.out.println(textArea.getInsets());

        final JTextPane textPane = new JTextPane();
        textPane.setText( text );
        textPane.setEditable( false );
        center.add( createScrollPane( textPane )  );
        textPane.setMargin( new Insets(5, 3, 7, 3) );
        System.out.println(textPane.getInsets());

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(center, BorderLayout.CENTER);
        frame.setSize(500, 200);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        Timer timer = new Timer(2000, new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                try
                {
                    Date now = new Date();
                    textArea.getDocument().insertString(textArea.getDocument().getLength(), "\n" + now.toString(), null);
                    textPane.getDocument().insertString(textPane.getDocument().getLength(), "\n" + now.toString(), null);
                }
                catch (BadLocationException e1) {}
            }
        });
        timer.start();
    }

    private static JComponent createScrollPane(JComponent component)
    {
        JScrollPane scrollPane = new JScrollPane(component);
        new ScrollControl( scrollPane );

        return scrollPane;
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowUI();
            }
        });
    }
}

編集:

スクロールペインの高さが低くなっても機能するようにコードを更新しました。何らかの理由で、デフォルトの動作では、スクロールバーが1行上に移動します。これは、新しいテキストが追加されたときにスクロールバーが下部に留まらないことを意味します。

于 2013-02-26T18:22:50.367 に答える
0

古いAppleMacOSの方法は、(いくつかの)自動分割ペインを下から上向きに引き裂いて再び落とすことができる可能性でした。

同じStyledDocumentに複数のビュー(テキストボックス)を作成し、そのうち下のビューのみが自動的にスクロールします。

于 2013-02-26T16:08:00.017 に答える