프로젝트 프로젝트에서 다음과 같은 문제점이 있습니다. 문제의 원인을 파악하는 데 시간이 걸렸습니다.이 간단한 코드로이 코드를 재현 할 수 있습니다.HTML이 최대 값으로 올바르게 스크롤되지 않는 JScrollBar + JTextPane
동적으로 HTMLEditorKit을 사용하여 JTextPane에 내용을 추가하고 있습니다. 자동 스크롤을 수동으로 제어하기 때문에 (사용자가 스크롤을 올렸을 때, 중지 할 때, 이벤트가 다시 활성화되도록 트리거 될 때) 자동 스크롤을 설정합니다.
지금 문제는 JScrollBar의 값을 최대 값으로 설정하면 HTMLDocument에 내용이 삽입 된 후 순간적으로 달라집니다. 다시 setValue를 다시 두 번째로 트리거하면 올바른 최대 값으로 스크롤됩니다.
JScrollBar가 HTMLDocument에 추가 한 바로 직후에 올바른 maximumValue를 인식하지 못하고 나중에 (지연된) 시간이 지난 것으로 보입니다. 그것은 또한 제대로 작동하지 않기 때문에
caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
사용
는, 해결책이 아니다. 최대 값까지 스크롤하지 않기 때문에 아래에 뷰 픽셀이 남기 때문에 원하지 않습니다.
다음은 문제를 재현 한 전체 코드입니다. 오른쪽 버튼 (& 스크롤 추가)을 클릭하면 DIV 요소가 본문에 삽입됩니다. 마지막 가시 선에 도달하면 마지막 최대 값으로 올바르게 스크롤되지 않으며 마지막 선은 숨겨집니다. 그러나 두 번째 scrollToEnd()를 트리거하기 위해 왼쪽 버튼을 수동으로 클릭하면 스크롤이 최대 값까지 올 바릅니다.
코드 :
이 코드 교체 그래도 작동하지만 작은 간격도 제대로 최대 값으로 스크롤하지 나뭇잎/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package javaapplication26;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultCaret;
import javax.swing.text.Element;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
public class NewJFrame extends javax.swing.JFrame {
/**
* Creates new form NewJFrame
*/
public NewJFrame() {
initComponents();
this.setSize(500, 200);
this.setLocationRelativeTo(null);
this.jTextPane1.setEditorKit(new HTMLEditorKit());
this.jTextPane1.setContentType("text/html");
this.jTextPane1.setText("<html><body><div id=\"GLOBALDIV\"></div></body></html>");
this.jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
this.jScrollPane1.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
DefaultCaret caret = (DefaultCaret) this.jTextPane1.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
this.jScrollPane1.setAutoscrolls(false);
this.jTextPane1.setAutoscrolls(false);
}
private void scrollToEnd() {
this.jScrollPane1.getVerticalScrollBar().setValue(this.jScrollPane1.getVerticalScrollBar().getMaximum());
//this.jTextPane1.setCaretPosition(this.jTextPane1.getDocument().getLength());
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
jPanel1 = new javax.swing.JPanel();
jScrollPane1 = new javax.swing.JScrollPane();
jTextPane1 = new javax.swing.JTextPane();
jPanel2 = new javax.swing.JPanel();
jButton1 = new javax.swing.JButton();
jButton2 = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jPanel1.setLayout(new java.awt.BorderLayout());
jScrollPane1.setViewportView(jTextPane1);
jPanel1.add(jScrollPane1, java.awt.BorderLayout.CENTER);
getContentPane().add(jPanel1, java.awt.BorderLayout.CENTER);
jButton1.setText("Scroll to end");
jButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton1ActionPerformed(evt);
}
});
jPanel2.add(jButton1);
jButton2.setText("Add & scroll");
jButton2.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton2ActionPerformed(evt);
}
});
jPanel2.add(jButton2);
getContentPane().add(jPanel2, java.awt.BorderLayout.PAGE_END);
pack();
}// </editor-fold>
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
try {
HTMLDocument doc = (HTMLDocument) this.jTextPane1.getDocument();
HTMLEditorKit editorKit = (HTMLEditorKit) this.jTextPane1.getEditorKit();
SecureRandom random = new SecureRandom();
String htmlCode = "<div style=\"background-color: #FFFF22; height: 12px; font-size: 12;\">"+new BigInteger(64, random).toString(64)+"</div>";
//editorKit.insertHTML(doc, doc.getLength(), htmlCode, 0, 0, null);
Element element = doc.getElement("GLOBALDIV");
if (element != null) {
doc.insertBeforeEnd(element, htmlCode);
}
this.scrollToEnd();
} catch (BadLocationException ex) {
Logger.getLogger(NewJFrame.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(NewJFrame.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
this.scrollToEnd();
}
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
/* Set the Nimbus look and feel */
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new NewJFrame().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JButton jButton1;
private javax.swing.JButton jButton2;
private javax.swing.JPanel jPanel1;
private javax.swing.JPanel jPanel2;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTextPane jTextPane1;
// End of variables declaration
}
:
this.jTextPane1.setCaretPosition(0);
this.jTextPane1.setCaretPosition(this.jTextPane1.getDocument().getLength());
대단히 감사합니다! 이것은 문제를 해결하는 것 같습니다! 이게 올바른 방법인가요? 초당 몇 줄과 같이 많은 양의 콘텐츠를 동적으로 창에 추가하면 어떨까요? 모든 scrollToEnd()에 대해 새로운 Runnable을 만드는 모든 것을 "날려 버릴"것인가? –
정확한지 여부는 정확한 사용 사례에 따라 다릅니다. 모델을 업데이트 한 다음보기가 올바르게 작동한다는 것은 정확합니다. 모델 업데이트가보기 업데이트에서 분리되므로보기가 올바르게 작동하지 않는 경우가 드물게있을 수 있습니다 (예 : 모델에 대한 몇 가지 충돌 업데이트가 올바른 순서로 GUI를 업데이트하지 않는 경우. 그러나 실행 가능성을 실행하는 EDT 이벤트가 생성 된 순서대로 이벤트 대기열로 정렬되므로 이는 거의 불가능합니다. –
초당 몇 줄과 같이 더 높은 부하에 대해서는 괜찮을 것입니다. Runnable을 인스턴스화하는 것은 비싸지 않다. 새 스레드를 만들지 않기 때문이다. Runnables의'run()'메소드가 EDT 쓰레드에서 호출되므로 새로운 쓰레드는 생성되지 않는다. 각각의'invokeLater'에 대해 새로운 객체가 생기지 만, 모든 스윙 이벤트는 똑같은 문제를 가지고 있습니다. 가능하면 여러 모델 업데이트를 하나의'scrollToEnd()'업데이트에 집계하는 것이 도움이되지만 문서를 변경하는 로직에 따라 다릅니다. –