2013-06-02 8 views
6

Ho un controllo ListView in una finestra di dialogo modale JavaFX 2.JavaFX 2.2: Come forzare un ridisegno/aggiornamento di un ListView

Questo ListView visualizza le istanze DXAlias, le ListCells per le quali sono fabbricati da una cella di produzione. La cosa principale che fanno gli oggetti factory è esaminare i dati delle proprietà UserData di ListView e confrontarli con l'elemento corrispondente a ListCell. Se sono uguali, i contenuti di ListCell sono resi in rosso, altrimenti neri. Lo faccio per indicare quale degli elementi in ListView è attualmente selezionato come "predefinito". Qui è la mia classe fabbrica ListCell modo da poter vedere quello che voglio dire:

private class AliasListCellFactory implements 
    Callback<ListView<DXSynonym>, ListCell<DXSynonym>> { 

@Override 
public ListCell<DXSynonym> call(ListView<DXSynonym> p) { 
    return new ListCell<DXSynonym>() { 

    @Override 
    protected void updateItem(DXSynonym item, boolean empty) { 
     super.updateItem(item, empty); 

     if (item != null) { 
      DXSynonym dx = (DXSynonym) lsvAlias.getUserData(); 

      if (dx != null && dx == item) { 
       this.setStyle("-fx-text-fill: crimson;");      
      } else { this.setStyle("-fx-text-fill: black;"); } 

      this.setText(item.getDxName()); 

     } else { this.setText(Census.FORMAT_TEXT_NULL); } 
    }}; 
} 

Ho un gestore di pulsante chiamata "handleAliasDefault()" che rende l'elemento selezionato in ListView nuova impostazione predefinita prendendo l'istanza DXAlias ​​selezionato e memorizzandolo in ListView: lsvAlias.setUserData (DXAlias ​​selezionato). Ecco il codice del gestore:

// Handler for Button[fx:id="btnAliasDefault"] onAction 
    @FXML 
    void handleAliasDefault(ActionEvent event) { 

     int sel = lsvAlias.getSelectionModel().getSelectedIndex(); 
     if (sel >= 0 && sel < lsvAlias.getItems().size()) { 
      lsvAlias.setUserData(lsvAlias.getItems().get(sel)); 
     } 
    } 

perché il cambiamento che è fatto in risposta al clic sul pulsante Imposta predefinito è quello di cambiare UserData del ListView() senza alcuna modifica al ObservableList supporto, la lista non indica correttamente il nuovo predefinito.

C'è un modo per forzare un ListView a ri-renderizzare i suoi ListCell? Ci sono un quadrilione di domande da parte degli utenti Android su questo argomento, ma non sembra esserci felicità per JavaFX. Potrei dover apportare un "cambiamento senza significato" all'array di supporto per forzare un ridisegno.

vedo che questo è stato chiesto per JavaFX 2.1: Javafx ListView refreshing

risposta

5

Per ora, sono in grado di ottenere il ListView da ridisegnare e indicare correttamente il valore predefinito selezionato utilizzando il metodo seguente, chiamato forceListRefreshOn (), a mio gestore del pulsante:

@FXML 
void handleAliasDefault(ActionEvent event) { 

    int sel = lsvAlias.getSelectionModel().getSelectedIndex(); 
    if (sel >= 0 && sel < lsvAlias.getItems().size()) { 
     lsvAlias.setUserData(lsvAlias.getItems().get(sel)); 
     this.<DXSynonym>forceListRefreshOn(lsvAlias); 
    } 
} 

il metodo di supporto appena scambia l'ObservableList dalla ListView e poi scambia indietro dentro, presumibilmente costringendo il ListView per aggiornare i suoi ListCells:

private <T> void forceListRefreshOn(ListView<T> lsv) { 
    ObservableList<T> items = lsv.<T>getItems(); 
    lsv.<T>setItems(null); 
    lsv.<T>setItems(items); 
} 
0

pare, non è necessario utilizzare updateItem metodo. Quello che ti serve - è quello di memorizzare l'indicatore della cella attualmente predefinita (che è ora nei dati dell'utente), in OblectProperty. E aggiungi un listener su questa proprietà da ogni cella. Quando il valore cambia, riassegna uno stile.

Ma penso che tale associazione chiamerà un altro problema: durante lo scorrimento, verranno create nuove celle, ma quelle precedenti non lasceranno l'associazione, che può causare perdite di memoria. Quindi è necessario aggiungere listener sulla rimozione delle celle dalla scena. Penso che possa essere fatto aggiungendo un listener su parentProperty, quando diventa nullo - rimuovi il bind.

UI non dovrebbe essere forzato ad essere aggiornato. Viene aggiornato automaticamente, quando vengono modificate le proprietà/rendering dei nodi esistenti. Quindi è sufficiente aggiornare l'aspetto/la proprietà dei nodi esistenti (celle). E per non dimenticare che le celle possono essere create in modo massiccio, durante lo scorrimento/rerendering, ecc.

+0

non ha mostrato il codice, ma i dati per ListView si trovano in una ObservableList. Ciascuno degli elementi del modello di dati è una proprietà JavaFX. Il contratto per ListView dice che quando i dati cambiano, la vista viene automaticamente aggiornata. Non sembra dire nulla su cosa dovrebbe accadere quando il modo in cui i dati vengono visualizzati cambia quando i dati stessi non lo fanno. Aggiungere una nuova associazione può fornire una soluzione ... ma a scapito di un codice molto brutto e, forse, fragile. Tutto quello di cui ho bisogno è che i ListCell vengano ridisegnati quando il default selezionato cambia. – scottb

+0

Questa è una proprietà comune di come funziona javafx 2: quando il nodo di scenegraph (cella particolare) modifica le sue proprietà, in modo che influenzi sulla rappresentazione visiva, scenegraph ridisegna quei nodi (celle) se necessario. Quindi, quando aggiorni l'aspetto visivo delle celle, queste verranno ridisegnate da scenegraph. –

+0

Vedo quello che stai dicendo ... ma non penso nemmeno che dovrei essere costretto a rifattorizzare il mio modello di dati per includere una proprietà che non è in realtà una parte del mio modello di dati. Voglio solo che l'alias predefinito selezionato sia reso in rosso, invece che nero. Il mio problema sarebbe stato risolto se ci fosse un semplice metodo a mia disposizione: lsvAlias.requestRefresh(); – scottb

12

Non è sicuro se questo funziona in Jav aFX 2.2, ma lo fa in JavaFX 8 e mi ci è voluto un po 'per capire. È necessario creare il proprio ListViewSkin e aggiungere un metodo refresh come:

public void refresh() { 
    super.flow.recreateCells(); 
} 

Questo chiamerà updateItem senza dover sostituire l'intera collezione osservabile.

Inoltre, per utilizzare la nuova pelle, è necessario creare un'istanza di esso e metterlo sul metodo del controller initialize nel caso in cui si sta utilizzando FXML:

MySkin<Subscription> skin = new MySkin<>(this.listView); // Injected by FXML 
this.listView.setSkin(skin); 
... 
((MySkin) listView.getSkin()).refresh(); // This is how you use it  

ho trovato questo dopo il debug il comportamento di una fisarmonica. Questo comando aggiorna il ListView che contiene ogni volta che lo espandi.

+0

Sembra funzionare correttamente su JavaFX 2.2. –

+1

Ecco come viene utilizzato su uno dei miei progetti OSS: https://github.com/Eldelshell/JobHunter/blob/master/jobhunter/src/main/java/jobhunter/gui/UpdateableListViewSkin.java – Eldelshell

10

Per qualsiasi altro corpo che finisce in questa pagina:

Il modo corretto di fare questo è quello di fornire l'elenco osservabile con un "estrattore" richiamata. Questo segnalerà la lista (e ListView) di qualsiasi modifica di proprietà.

classe personalizzata da visualizzare:

public class Custom { 
    StringProperty name = new SimpleStringProperty(); 
    IntegerProperty id = new SimpleIntegerProperty(); 

    public static Callback<Custom, Observable[]> extractor() { 
     return new Callback<Custom, Observable[]>() { 
      @Override 
      public Observable[] call(Custom param) { 
       return new Observable[]{param.id, param.name}; 
      } 
     }; 
    } 

    @Override 
    public String toString() { 
     return String.format("%s: %s", name.get(), id.get()); 
    } 
} 

E il tuo corpo codice principale:

ListView<Custom> myListView; 
//...init the ListView appropriately 
ObservableList<Custom> items = FXCollections.observableArrayList(Custom.extractor()); 
myListView.setItems(items); 
Custom item = new Custom(); 
items.add(item); 
item.name.set("Mickey Mouse"); 
//^Should update your ListView!!! 

See (e metodi simili): https://docs.oracle.com/javafx/2/api/javafx/collections/FXCollections.html#observableArrayList(javafx.util.Callback)

+0

potresti spiegare come l'utilizzo di SimpleIntegerProperty è utilizzato nell'esempio – serup

+1

@serup Non sono del tutto sicuro di quale sia la tua domanda, ma lascia che provi a spiegare. Nel mio esempio, non ho mai impostato in modo esplicito il valore dell'ID, quindi il valore predefinito era 0. Questo risulterebbe in ListView sulla riga da visualizzare "Mickey Mouse: 0". Se si desidera impostare il valore ID, è possibile utilizzare 'item.name.set (6)', ad esempio, e si vedrà l'aggiornamento ListValue correttamente. A seconda del caso d'uso, l'utilizzo di un costruttore non predefinito sulla classe 'Custom' sarebbe probabilmente" migliore ". –

Problemi correlati