web-dev-qa-db-fra.com

JavaFX TextArea et défilement automatique

J'essaie de faire défiler automatiquement un TextArea vers le bas avec un nouveau texte qui est inséré via un gestionnaire d'événements. Chaque nouvelle entrée est juste une longue chaîne de texte, chaque entrée étant séparée par un saut de ligne. J'ai essayé un gestionnaire de modifications qui définit setscrolltop sur Double.MIN_VALUE mais en vain. Des idées sur la façon dont cela pourrait être fait?

15
Sam Keays

Vous devez ajouter un écouteur à l'élément TextArea pour faire défiler l'écran jusqu'en bas lorsque sa valeur est modifiée:

@FXML private TextArea txa; 

...

txa.textProperty().addListener(new ChangeListener<Object>() {
    @Override
    public void changed(ObservableValue<?> observable, Object oldValue,
            Object newValue) {
        txa.setScrollTop(Double.MAX_VALUE); //this will scroll to the bottom
        //use Double.MIN_VALUE to scroll to the top
    }
});

Mais cet écouteur n'est pas déclenché lorsque vous utilisez la méthode setText(text). Par conséquent, si vous souhaitez le déclencher après une setText(text), utilisez la appendText(text) juste après:

txa.setText("Text into the textArea"); //does not trigger the listener
txa.appendText("");  //this will trigger the listener and will scroll the
                     //TextArea to the bottom

Cela ressemble plus à un bogue, une fois que setText() devrait déclencher l'écouteur changed, mais ce n'est pas le cas. C’est la solution que j’utilise moi-même et j’espère que cela vous aidera.

25
Math

txa.appendText ("") défilera vers le bas sans auditeur . Cela devient un problème si vous voulez revenir en arrière et que le texte est constamment mis à jour. txa.setText ("") place la barre de défilement en haut et le même problème s’applique.

Ma solution consistait à étendre la classe TextArea, en modifiant la balise FXML de textArea à LogTextArea. Là où cela fonctionne, cela pose clairement des problèmes au créateur de scène car il ne sait pas ce qu'est ce composant.

import javafx.scene.control.TextArea;
import javafx.scene.text.Font;

public class LogTextArea extends TextArea {

private boolean pausedScroll = false;
private double scrollPosition = 0;

public LogTextArea() {
    super();
}

public void setMessage(String data) {
    if (pausedScroll) {
        scrollPosition = this.getScrollTop();
        this.setText(data);
        this.setScrollTop(scrollPosition);
    } else {
        this.setText(data);
        this.setScrollTop(Double.MAX_VALUE);
    }
}

public void pauseScroll(Boolean pause) {
    pausedScroll = pause;
}

}
9
jamesarbrown

Alternative à cet étrange bogue setText sans utiliser appendText

textArea.selectPositionCaret(textArea.getLength());
textArea.deselect(); //removes the highlighting
4

Un ajout que j’ajouterais à la réponse de jamesarbrown serait d’utiliser une propriété booléenne à la place pour que vous puissiez y accéder depuis FXML. Quelque chose comme ça.

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.TextArea;

public class LogTextArea extends TextArea {
    private final BooleanProperty pausedScrollProperty = new SimpleBooleanProperty(false);
    private double scrollPosition = 0;

    public LogTextArea() {
        super();
    }

    public void setMessage(String data) {
        if (isPausedScroll()) {
            scrollPosition = this.getScrollTop();
            this.setText(data);
            this.setScrollTop(scrollPosition);
        } else {
            this.setText(data);
            this.setScrollTop(Double.MAX_VALUE);
        }
    }

    public final BooleanProperty pausedScrollProperty() { return pausedScrollProperty; }
    public final boolean isPausedScroll() { return pausedScrollProperty.getValue(); }
    public final void setPausedScroll(boolean value) { pausedScrollProperty.setValue(value); }
}

Cependant, le problème avec cette réponse est que si vous êtes submergé par une quantité d'entrée excessivement importante (comme cela peut se produire lors de l'extraction d'un journal depuis un flux IO, le thread javaFX se bloquera car l'objet TextArea sera trop beaucoup de données.

1
Jonathan Leitschuh

Je n'ai pas assez de réputation pour commenter, mais je voulais donner aux futurs lecteurs un aperçu de la raison pour laquelle setText ne semble pas déclencher l'auditeur, mais appendText le fait, comme dans la réponse de Math. 

Je viens de trouver cette réponse alors que je rencontrais moi-même des problèmes similaires et examinais le code. Ceci est actuellement le meilleur résultat de 'javafx textarea settext scroll' dans une recherche Google.

setText déclenche effectivement l'écouteur. Selon le javadoc de la méthode doSet dans TextInputControl (la superclasse de TextArea):

     * doSet is called whenever the setText() method was called directly
     * on the TextInputControl, or when the text property was bound,
     * unbound, or reacted to a binding invalidation. It is *not* called
     * when modifications to the content happened indirectly, such as
     * through the replaceText / replaceSelection methods.

Dans la méthode doSet, un appel à updateText () est effectué, que TextArea substitue:

  @Override final void textUpdated() {
        setScrollTop(0);
        setScrollLeft(0);
    }  

Ainsi, lorsque vous définissez le montant de défilement dans l'écouteur comme dans la réponse de Math, les événements suivants se produisent:

  1. Le TextProperty est mis à jour
  2. Votre auditeur est appelé et le parchemin est défini.
  3. doSet s'appelle
  4. textUpdated s'appelle
  5. Le parchemin est replacé en haut à gauche

Quand vous ajoutez ensuite "",

  1. Le TextProperty est mis à jour
  2. Votre auditeur est appelé et le parchemin est défini.

Le javadoc est au-dessus est clair pourquoi c'est le cas - doSet est appelé uniquement lors de l'utilisation de setText. En fait, appendText appelle insertText qui appelle replaceText - et le javadoc indique en outre que replaceText ne déclenche PAS un appel à doSet.

Le comportement est plutôt irritant, d’autant plus que ce sont toutes des méthodes finales, qui ne sont pas évidentes au premier abord - mais ne constituent pas un bug.

1
Matthew