2015-07-14 15 views
6

Possiedo un'applicazione JavaFX e un'attività di concorrenza. mentre l'attività è in esecuzione, voglio aggiungere il messaggio da UpdateMessage() per un TextAreaJavaFX ChangeListener non funziona sempre

perché l'associazione non accodare il nuovo testo al TextArea, ho usato un ChangeListener

worker.messageProperty().addListener((observable, oldValue, newValue) -> { 
    ta_Statusbereich.appendText("\n" + newValue); 
}); 

Questo è lavorando ma non su ogni cambiamento. Ho controllato con uno System.out.println() e contato nella attività dal 1 al 300

for (Integer i = 1; i <= 300; i++) { 
    updateMessage(i.toString()); 
    System.out.println(i.toString()); 
} 

questo println() nel Task mi dà quello che voglio 1,2,3,4,5 , 6,7,8 e così via, ma il mio TextArea mostra 1,4,5,8,9 ho quindi aggiunto un println nel ChangeListener e ottenere lo stesso risultato, 1,4,5,8,9 (il il risultato è casuale non sempre 1,4,5 ...)

perché? ci sono altri modi per aggiungere il testo del messaggio a TextAres, magari con bind?

+0

"Perché?": Ha spiegato pienamente alla [documentazione] (http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html#updateMessage -java.lang.String-). Cosa vuoi veramente fare qui? (Suppongo che non sia solo per visualizzare i valori 1-300 in un'area di testo, in quanto non sarebbe necessario un compito.) –

+0

l'attività rinomina i file in una cartella selezionata sulla rete e cerco di elencare tutti i file elaborati nel textarea. come una configurazione che stampa i file copiati. – Garog

risposta

14

La proprietà message è concepito come una proprietà che contiene un "messaggio corrente" per la task: vale a dire il caso d'uso di destinazione è qualcosa di simile ad un messaggio di stato. In questo caso d'uso, non importa se un messaggio che è memorizzato nella proprietà solo per un tempo molto breve non viene mai intercettato. Infatti, i documentation for updateMessage() stati:

chiamate ai UpdateMessage sono coalizzati ed eseguiti successivamente sul thread dell'applicazione FX , così chiamate a UpdateMessage, anche dal thread dell'applicazione FX , non necessariamente comportare aggiornamenti immediati per questo proprietà e valori di messaggio intermedio possono essere uniti a salvare sulle notifiche di evento.

(il mio accento). Quindi, in breve, alcuni valori passati a updateMessage(...) potrebbero non essere mai effettivamente impostati come il valore di messageProperty se vengono sostituiti rapidamente con un altro valore. In generale, è possibile che venga visualizzato un solo valore ogni volta che un frame viene visualizzato sullo schermo (60 volte al secondo o meno). Se hai un caso d'uso in cui è importante che tu voglia osservare ogni valore, allora devi usare un altro meccanismo.

Un'implementazione molto ingenua dovrebbe semplicemente utilizzare Platform.runLater(...) e aggiornare direttamente l'area di testo. Non raccomando questa implementazione, poiché rischi di inondare il thread dell'applicazione FX con troppe chiamate (il motivo esatto per cui updateMessage(...) coalizza le chiamate), rendendo l'interfaccia utente insensibile. Tuttavia, questa implementazione sarà simile:

for (int i = 1 ; i <= 300; i++) { 
    String value = "\n" + i ; 
    Platform.runLater(() -> ta_Statusbereich.appendText(value)); 
} 

Un'altra opzione è quella di rendere ogni operazione un'attività separata, e li esegue in parallelo in qualche esecutore. Aggiungi all'area di testo nel gestore onSucceeded di ciascuna attività. In questa implementazione, l'ordine dei risultati non è predeterminato, per cui se l'ordine è importante, non si tratta di un meccanismo appropriato:

final int numThreads = 8 ; 
Executor exec = Executors.newFixedThreadPool(numThreads, runnable -> { 
    Thread t = Executors.defaultThreadFactory().newThread(runnable); 
    t.setDaemon(true); 
    return t ; 
}); 

// ... 

for (int i = 1; i <= 300; i++) { 
    int value = i ; 
    Task<String> task = new Task<String>() { 
     @Override 
     public String call() { 
      // in real life, do real work here... 
      return "\n" + value ; // value to be processed in onSucceeded 
     } 
    }; 
    task.setOnSucceeded(e -> ta_Statusbereich.appendText(task.getValue())); 
    exec.execute(task); 
} 

Se si vuole fare tutto questo da una singola attività, e controllare l'ordine, quindi è possibile inserire tutti i messaggi in un BlockingQueue, prelevando i messaggi dalla coda di blocco e posizionandoli nell'area di testo sul thread dell'applicazione FX.Per assicurarti di non sovraccaricare il thread dell'applicazione FX con troppe chiamate, dovresti consumare i messaggi dalla coda non più di una volta per rendering di frame sullo schermo. È possibile utilizzare uno AnimationTimer per questo scopo: è garantito il richiamo del metodo handle una volta per il rendering del frame. Questo appare come:

BlockingQueue<String> messageQueue = new LinkedBlockingQueue<>(); 

Task<Void> task = new Task<Void>() { 
    @Override 
    public Void call() throws Exception { 
     final int numMessages = 300 ; 
     Platform.runLater(() -> new MessageConsumer(messageQueue, ta_Statusbereich, numMessages).start()); 
     for (int i = 1; i <= numMessages; i++) { 
      // do real work... 
      messageQueue.put(Integer.toString(i)); 
     } 
     return null ; 
    } 
}; 
new Thread(task).start(); // or submit to an executor... 

// ... 

public class MessageConsumer extends AnimationTimer { 
    private final BlockingQueue<String> messageQueue ; 
    private final TextArea textArea ; 
    private final numMessages ; 
    private int messagesReceived = 0 ; 
    public MessageConsumer(BlockingQueue<String> messageQueue, TextArea textArea, int numMessages) { 
     this.messageQueue = messageQueue ; 
     this.textArea = textArea ; 
     this.numMessages = numMessages ; 
    } 
    @Override 
    public void handle(long now) { 
     List<String> messages = new ArrayList<>(); 
     messagesReceived += messageQueue.drainTo(messages); 
     messages.forEach(msg -> textArea.appendText("\n"+msg)); 
     if (messagesReceived >= numMessages) { 
      stop(); 
     } 
    } 
} 
+2

risposte come questa sono la ragione per cui chiedo su questa piattaforma! wow e grande, grazie mille. Per persone come me che hanno solo qualche sciolismo. che contiene così tante informazioni e conoscenze ... quando devi scavare quella roba dalle descrizioni degli api ... diavolo uomo ... ho provato il runlater ma ho ottenuto esattamente quello che hai previsto quando hai rinominato i file sulla macchina locale e non il network. anche l'ordine è importante, perché conto i file e stampo il numero sullo schermo. quindi alla fine devo scegliere il numero 3. :) – Garog

+0

Questa è un'ottima risposta! Mi ha aiutato con la situazione che ho esattamente! l'opzione 3 è la strada da percorrere! – WillZ

+1

Sì, questa è un'ottima risposta. L'opzione 3 era il modo migliore per me. Tuttavia, usando 'messages.forEach (msg -> textArea.appendText (" \ "+ msg));' è stato un po 'lento per me. Invece, ho concatenato tutte le righe prima di aggiungerle all'area di testo, ad esempio textArea.appendText (String.join ("", messages)) '. Non è necessario aggiungere un carattere di nuova riga perché i miei messaggi contengono già nuove righe. – Thylossus