2010-04-19 15 views
10

Estoy intentando imitar la funcionalidad de Adium y de la mayoría de otros clientes de chat que he visto, donde las barras de desplazamiento avanzan hacia abajo cuando ingresan nuevos mensajes, pero solo si ya estás allí. En otras palabras, si ha desplazado algunas líneas hacia arriba y está leyendo, cuando entra un nuevo mensaje, no saltará su posición hasta la parte inferior de la pantalla; eso sería molesto Pero si se desplaza hacia abajo, el programa correctamente asume que desea ver los mensajes más recientes en todo momento y, por lo tanto, se desplaza automáticamente en consecuencia.Swing: Desplácese hasta la parte inferior de JScrollPane, condicional en la ubicación de la ventana actual

He tenido un mal momento tratando de imitar esto; la plataforma parece luchar contra este comportamiento a toda costa. Lo mejor que puedo hacer es la siguiente:

En 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); 

En método que controla el texto nuevo que entra:

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()); 
} 

Ahora, esto funciona, pero es janky y no es ideal por dos razones.

  1. Al configurar la posición de intercalación, se borrará cualquier selección que el usuario pueda tener en el área de chat. Me imagino que esto sería muy irritante si está intentando copiar/pegar.
  2. Dado que el avance del panel de desplazamiento se produce después de insertar el texto, hay una fracción de segundo donde la barra de desplazamiento está en la posición incorrecta, y luego salta visualmente hacia el final. Esto no es ideal

antes de preguntar, sí que he leído esta entrada del blog en Text Area Scrolling, pero el desplazamiento predeterminado a un comportamiento inferior no es lo que quiero.

Otros (pero a mi mente, no del todo útil en este sentido) preguntas relacionadas: Setting scroll bar on a jscrollpane Making a JScrollPane automatically scroll all the way down.

Cualquier ayuda en este sentido sería muy apreciada.

Editar:

De acuerdo con el consejo de Devon_C_Miller, tengo una forma mejorada de desplazarse a la parte inferior, la resolución de la edició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); 
      } 
     } 
    }); 
} 

I still have problem # 2.

+0

Puede comprobar mi respuesta allí: http://stackoverflow.com/questions/4045722/how-to-make-jtextpane-autoscroll-only-when-scroll-bar-is -at-bottom-and-scroll-lo/23654546 # 23654546 –

Respuesta

6

Tome un vistazo a la scrollRectToVisible(Rectangle r) y modelToView(int pos)

Eso debe conseguir lo que está buscando sin molestar a la selección del usuario.

En cuanto a la barra de desplazamiento, intente forzar que el desplazamiento y la adición se produzcan en diferentes eventos. Por ejemplo:

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

No entiendo lo que quieres decir sobre los diferentes eventos ... Me desplazo como un evento separado. – I82Much

+0

El objetivo de dividir el desplazamiento y el apéndice es permitir que la GUI repinte entre el desplazamiento y la adición del texto. Eso permite que la barra de desplazamiento se mueva hacia la parte inferior antes de que aparezca el texto. –

+0

Estoy dividiendo el desplazamiento y anexando, pero claramente el apéndice tiene que ocurrir antes del desplazamiento, de lo contrario no estaremos en la parte inferior de la vista cuando el texto se anexe. – I82Much

1

Echa un vistazo a este example.

Aunque, me gustaría cambiar el código para utilizar la siguiente que es más eficiente:

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

Ese ejemplo descarta nuevamente la selección. Además, la barra de desplazamiento todavía salta. – I82Much

+0

No veo la "barra de desplazamiento saltando". Siempre puede cambiar el código para NO restablecer la posición de intercalación si se encuentra una selección. Entonces la ventana gráfica no se desplazará automáticamente, pero creo que ese es el comportamiento que desea. Si un usuario está actualmente seleccionando texto, el desplazamiento no debe ser automático. – camickr

+0

@camickr El enlace que proporcione no funciona. –

3

Sobre la base de las respuestas anteriores y más Google'ing hice una prueba en la que un hilo añade continuamente cadenas a un JTextArea en un JScrollPane. Creo que es importante usar invokeLater aquí, ya que JScrollBar necesita ser actualizado con su nuevo valor máximo antes de que nos desplacemos a ese valor. Usando invokeLater, AWT Thread se encargará de esto.

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

He pasado los últimos días probando diferentes enfoques para este problema exacto. La mejor solución que he encontrado es reemplazar el administrador de diseño de la ventana gráfica de JScrollPane e implementar allí todo el comportamiento de desplazamiento deseado. La perilla de la barra de desplazamiento se ha ido, y está ardiendo rápido, tan rápido que creó otra condición de carrera en la que tuve que trabajar. Los detalles están aquí: 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

Un poco tarde pero aquí es algo que me encontré: http://www.camick.com/java/source/SmartScroller.java

En esencia se puede utilizar siguiente código :

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

Y aquí th Clase E SmartScroller:

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; 
    } 
} 
Cuestiones relacionadas