2010-04-19 40 views
10

Tôi đang cố gắng bắt chước chức năng của Adium và hầu hết các ứng dụng trò chuyện khác mà tôi đã thấy, trong đó thanh cuộn tiến xuống phía dưới khi có tin nhắn mới, nhưng chỉ nếu bạn đã ở đó. Nói cách khác, nếu bạn đã cuộn một vài dòng lên và đang đọc, khi một tin nhắn mới đến trong nó sẽ không nhảy vị trí của bạn vào dưới cùng của màn hình; điều đó sẽ gây phiền nhiễu. Nhưng nếu bạn được cuộn xuống dưới cùng, chương trình sẽ giả định rằng bạn muốn xem các thư gần đây nhất mọi lúc, và vì vậy tự động cuộn tương ứng.Swing: Cuộn xuống dưới cùng của JScrollPane, có điều kiện trên vị trí chế độ xem hiện tại

Tôi đã có một khoảng thời gian cố bắt chước điều này; nền tảng dường như chống lại hành vi này bằng mọi giá. Điều tốt nhất tôi có thể làm là như sau:

Trong constructor:

JTextArea chatArea = new JTextArea(); 
JScrollPane chatAreaScrollPane = new JScrollPane(chatArea); 

// We will manually handle advancing chat window 
DefaultCaret caret = (DefaultCaret) chatArea.getCaret(); 
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); 

Trong phương pháp để xử lý văn bản mới sắp tới trong:

boolean atBottom = isViewAtBottom(); 

// Append the text using styles etc to the chatArea 

if (atBottom) { 
    scrollViewportToBottom(); 
} 


public boolean isAtBottom() { 
    // Is the last line of text the last line of text visible? 
    Adjustable sb = chatAreaScrollPane.getVerticalScrollBar(); 

    int val = sb.getValue(); 
    int lowest = val + sb.getVisibleAmount(); 
    int maxVal = sb.getMaximum(); 

    boolean atBottom = maxVal == lowest; 
    return atBottom; 
} 


private void scrollToBottom() { 
    chatArea.setCaretPosition(chatArea.getDocument().getLength()); 
} 

Bây giờ, làm việc này, nhưng đó là janky và không lý tưởng Vì hai lý do.

  1. Bằng cách đặt vị trí dấu mũ, bất kỳ lựa chọn nào người dùng có thể có trong khu vực trò chuyện sẽ bị xóa. Tôi có thể tưởng tượng điều này sẽ rất khó chịu nếu anh ta cố gắng sao chép/dán.
  2. Vì sự tiến bộ của ngăn di chuyển xảy ra sau khi văn bản được chèn vào, có một giây thứ hai nơi thanh cuộn ở vị trí sai, và sau đó nó nhảy về phía cuối. Đây không phải là lý tưởng.

Trước khi bạn hỏi, có Tôi đã đọc bài đăng trên blog này trên Text Area Scrolling, nhưng cuộn mặc định cho hành vi dưới cùng không phải là những gì tôi muốn.

(nhưng tâm trí của tôi, chứ không phải hoàn toàn hữu ích trong vấn đề này) câu hỏi liên quan khác: Setting scroll bar on a jscrollpane Making a JScrollPane automatically scroll all the way down.

Bất kỳ sự giúp đỡ trong vấn đề này sẽ được rất nhiều đánh giá cao.

Edit:

Theo lời khuyên Devon_C_Miller, tôi có một cách cải tiến di chuyển xuống phía dưới, giải quyết vấn đề # 1.

private void scrollToBottom() { 
    javax.swing.SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      try { 
       int endPosition = chatArea.getDocument().getLength(); 
       Rectangle bottom = chatArea.modelToView(endPosition); 
       chatArea.scrollRectToVisible(bottom); 
      } 
      catch (BadLocationException e) { 
       System.err.println("Could not scroll to " + e); 
      } 
     } 
    }); 
} 

Tôi vẫn gặp sự cố # 2.

+0

Bạn có thể kiểm tra câu trả lời của tôi ở đó: http://stackoverflow.com/questions/4045722/how-to-make-jtextpane-autoscroll-only-when-scroll-bar-is -at-bottom-and-scroll-lo/23654546 # 23654546 –

Trả lời

6

Hãy nhìn vào các scrollRectToVisible(Rectangle r)modelToView(int pos)

Điều đó sẽ giúp bạn có được những gì bạn đang tìm kiếm mà không làm phiền lựa chọn của người dùng.

Đối với thanh cuộn, hãy thử buộc cuộn và phần phụ thêm vào xảy ra trên các sự kiện khác nhau. Ví dụ:

if (atBottom) { 
    // append new line & do scroll 
    SwingUtilities.invokerLater(new Runnable(){ 
     public void run() { 
      // append text 
     }}); 
} else { 
    // append newline 
    // append text 
} 
+0

Tôi không hiểu ý của bạn về các sự kiện khác nhau ... Tôi đang cuộn như một sự kiện riêng biệt. – I82Much

+0

Mục tiêu của việc chia nhỏ thanh cuộn và phần phụ thêm là cho phép GUI có cơ hội vẽ lại giữa cuộn và phần bổ sung của văn bản. Điều đó cho phép thanh cuộn di chuyển xuống dưới cùng trước khi văn bản xuất hiện. –

+0

Tôi đang chia nhỏ thanh cuộn và nối thêm, nhưng rõ ràng phần phụ thêm phải xảy ra trước khi cuộn, nếu không chúng tôi sẽ không ở cuối chế độ xem khi văn bản được thêm vào. – I82Much

1

Kiểm tra số example này.

Mặc dù, tôi sẽ thay đổi mã để sử dụng sau đó là hiệu quả hơn:

caret.setDot(textArea.getDocument().getLength()); 
+0

Ví dụ đó lại loại bỏ lựa chọn. Hơn nữa thanh cuộn vẫn nhảy. – I82Much

+0

Tôi không thấy "nhảy thanh cuộn". Bạn luôn có thể thay đổi mã để KHÔNG đặt lại vị trí dấu mũ nếu tìm thấy một lựa chọn. Sau đó, khung nhìn sẽ không tự động cuộn, nhưng tôi nghĩ đó là hành vi bạn muốn. Nếu người dùng hiện đang chọn văn bản thì việc cuộn không được tự động. – camickr

+0

@camickr Liên kết bạn cung cấp không hoạt động. –

3

Dựa trên câu trả lời trước và Google'ing hơn nữa tôi đã thực hiện một thử nghiệm trong đó một chủ đề liên tục gắn Strings đến một JTextArea trong một JScrollPane. Tôi nghĩ điều quan trọng là sử dụng invokeLater ở đây, vì JScrollBar cần phải được cập nhật với giá trị tối đa mới của nó trước khi chúng ta cuộn đến giá trị đó. Sử dụng invokeLater, Chủ đề AWT sẽ xử lý vấn đề này.

private class AppendThread extends Thread { 
    public void run() { 
     while (true) { 
     String s = "random number = " + Math.random() + "\n"; 
     boolean scrollDown = textAreaBottomIsVisible(); 
     textArea.append(s); 
     if (scrollDown) { 
      javax.swing.SwingUtilities.invokeLater(new Runnable() { 
        public void run() { 
         JScrollBar bar = scrollPane.getVerticalScrollBar(); 
         bar.setValue(bar.getMaximum()); 
        } 
       } 
     ); 
     } 
     try { 
      Thread.sleep(1000); 
     } catch (Exception e) { 
      System.err.println("exception = " + e); 
     } 
     } 
    } 

    private boolean textAreaBottomIsVisible() { 
     Adjustable sb = scrollPane.getVerticalScrollBar(); 
     int val = sb.getValue(); 
     int lowest = val + sb.getVisibleAmount(); 
     int maxVal = sb.getMaximum(); 
     boolean atBottom = maxVal == lowest; 
     return atBottom; 
    } 
} 
1

Tôi đã dành vài ngày qua để thử các cách tiếp cận khác nhau cho vấn đề chính xác này. Giải pháp tốt nhất mà tôi tìm thấy là thay thế trình quản lý bố cục của khung nhìn JScrollPane và thực hiện tất cả hành vi cuộn mong muốn ở đó. Nút thanh cuộn nhảy vọt đã biến mất, và nó nhanh quá nhanh - nó nhanh đến nỗi nó tạo ra một tình trạng đua khác mà tôi phải làm việc xung quanh. Thông tin chi tiết đang ở đây: http://www.java-forums.org/awt-swing/56808-scroll-bar-knob-jumpy-when-component-growing.html

0
DefaultCaret caret = (DefaultCaret)textArea.getCaret(); 
caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); 
1

Một chút muộn nhưng đây là một cái gì đó tôi tìm thấy: http://www.camick.com/java/source/SmartScroller.java

Về bản chất, bạn có thể sử dụng đoạn mã sau :

JScrollPane scrollPane = new JScrollPane(myOversizedComponent); 
// Injects smartscroll behaviour to your scrollpane 
new SmartScroller(scrollPane); 

Và tại đây e SmartScroller lớp:

import java.awt.Component; 
import java.awt.event.*; 
import javax.swing.*; 
import javax.swing.text.*; 

/** 
* The SmartScroller will attempt to keep the viewport positioned based on 
* the users interaction with the scrollbar. The normal behaviour is to keep 
* the viewport positioned to see new data as it is dynamically added. 
* 
* Assuming vertical scrolling and data is added to the bottom: 
* 
* - when the viewport is at the bottom and new data is added, 
* then automatically scroll the viewport to the bottom 
* - when the viewport is not at the bottom and new data is added, 
* then do nothing with the viewport 
* 
* Assuming vertical scrolling and data is added to the top: 
* 
* - when the viewport is at the top and new data is added, 
* then do nothing with the viewport 
* - when the viewport is not at the top and new data is added, then adjust 
* the viewport to the relative position it was at before the data was added 
* 
* Similiar logic would apply for horizontal scrolling. 
*/ 
public class SmartScroller implements AdjustmentListener 
{ 
    public final static int HORIZONTAL = 0; 
    public final static int VERTICAL = 1; 

    public final static int START = 0; 
    public final static int END = 1; 

    private int viewportPosition; 

    private JScrollBar scrollBar; 
    private boolean adjustScrollBar = true; 

    private int previousValue = -1; 
    private int previousMaximum = -1; 

    /** 
    * Convenience constructor. 
    * Scroll direction is VERTICAL and viewport position is at the END. 
    * 
    * @param scrollPane the scroll pane to monitor 
    */ 
    public SmartScroller(JScrollPane scrollPane) 
    { 
     this(scrollPane, VERTICAL, END); 
    } 

    /** 
    * Convenience constructor. 
    * Scroll direction is VERTICAL. 
    * 
    * @param scrollPane the scroll pane to monitor 
    * @param viewportPosition valid values are START and END 
    */ 
    public SmartScroller(JScrollPane scrollPane, int viewportPosition) 
    { 
     this(scrollPane, VERTICAL, viewportPosition); 
    } 

    /** 
    * Specify how the SmartScroller will function. 
    * 
    * @param scrollPane the scroll pane to monitor 
    * @param scrollDirection indicates which JScrollBar to monitor. 
    *       Valid values are HORIZONTAL and VERTICAL. 
    * @param viewportPosition indicates where the viewport will normally be 
    *       positioned as data is added. 
    *       Valid values are START and END 
    */ 
    public SmartScroller(JScrollPane scrollPane, int scrollDirection, int viewportPosition) 
    { 
     if (scrollDirection != HORIZONTAL 
     && scrollDirection != VERTICAL) 
      throw new IllegalArgumentException("invalid scroll direction specified"); 

     if (viewportPosition != START 
     && viewportPosition != END) 
      throw new IllegalArgumentException("invalid viewport position specified"); 

     this.viewportPosition = viewportPosition; 

     if (scrollDirection == HORIZONTAL) 
      scrollBar = scrollPane.getHorizontalScrollBar(); 
     else 
      scrollBar = scrollPane.getVerticalScrollBar(); 

     scrollBar.addAdjustmentListener(this); 

     // Turn off automatic scrolling for text components 

     Component view = scrollPane.getViewport().getView(); 

     if (view instanceof JTextComponent) 
     { 
      JTextComponent textComponent = (JTextComponent)view; 
      DefaultCaret caret = (DefaultCaret)textComponent.getCaret(); 
      caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); 
     } 
    } 

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

    /* 
    * Analyze every adjustment event to determine when the viewport 
    * needs to be repositioned. 
    */ 
    private void checkScrollBar(AdjustmentEvent e) 
    { 
     // The scroll bar listModel contains information needed to determine 
     // whether the viewport should be repositioned or not. 

     JScrollBar scrollBar = (JScrollBar)e.getSource(); 
     BoundedRangeModel listModel = scrollBar.getModel(); 
     int value = listModel.getValue(); 
     int extent = listModel.getExtent(); 
     int maximum = listModel.getMaximum(); 

     boolean valueChanged = previousValue != value; 
     boolean maximumChanged = previousMaximum != maximum; 

     // Check if the user has manually repositioned the scrollbar 

     if (valueChanged && !maximumChanged) 
     { 
      if (viewportPosition == START) 
       adjustScrollBar = value != 0; 
      else 
       adjustScrollBar = value + extent >= maximum; 
     } 

     // Reset the "value" so we can reposition the viewport and 
     // distinguish between a user scroll and a program scroll. 
     // (ie. valueChanged will be false on a program scroll) 

     if (adjustScrollBar && viewportPosition == END) 
     { 
      // Scroll the viewport to the end. 
      scrollBar.removeAdjustmentListener(this); 
      value = maximum - extent; 
      scrollBar.setValue(value); 
      scrollBar.addAdjustmentListener(this); 
     } 

     if (adjustScrollBar && viewportPosition == START) 
     { 
      // Keep the viewport at the same relative viewportPosition 
      scrollBar.removeAdjustmentListener(this); 
      value = value + maximum - previousMaximum; 
      scrollBar.setValue(value); 
      scrollBar.addAdjustmentListener(this); 
     } 

     previousValue = value; 
     previousMaximum = maximum; 
    } 
} 
Các vấn đề liên quan