it-swarm.com.de

JavaFX 2.2: So erzwingen Sie ein Neuzeichnen/Aktualisieren einer ListView

Ich habe ein ListView-Steuerelement in einem modalen JavaFX 2-Dialogfenster.

Diese ListView zeigt DXAlias-Instanzen an, deren ListCells von einer Zellenfactory hergestellt werden. Die Factory-Objekte untersuchen hauptsächlich die UserData-Eigenschaftsdaten der ListView und vergleichen sie mit dem der ListCell entsprechenden Element. Wenn sie identisch sind, wird der Inhalt der ListCell in Rot dargestellt, andernfalls in Schwarz. Ich tue dies, um anzuzeigen, welches der Elemente in der ListView derzeit als "Standard" ausgewählt ist. Hier ist meine ListCell-Factory-Klasse, damit Sie sehen können, was ich meine:

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

Ich habe eine Schaltflächenbehandlungsroutine mit dem Namen "handleAliasDefault ()", mit der das ausgewählte Element in der ListView zum neuen Standard wird, indem die ausgewählte DXAlias-Instanz in der ListView gespeichert wird: lsvAlias.setUserData (selected DXAlias). Hier ist der Handler-Code:

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

Da die Änderung, die als Reaktion auf das Klicken auf die Schaltfläche "Als Standard festlegen" vorgenommen wird, darin besteht, die UserData () der ListView zu ändern, ohne die Hintergrund-ObservableList zu ändern, gibt die Liste den neuen Standard nicht korrekt an.

Gibt es eine Möglichkeit, eine ListView zu zwingen, ihre ListCells neu zu rendern? Es gibt eine Billiarde Fragen von Android-Nutzern zu diesem Thema, aber es scheint kein Glück für JavaFX zu geben. Möglicherweise muss ich eine "bedeutungslose Änderung" am Backing-Array vornehmen, um ein Neuzeichnen zu erzwingen.

Ich sehe, dass dies für JavaFX 2.1 gefragt wurde: Javafx ListView Aktualisierung

8
scottb

Im Moment kann ich die ListView mit der folgenden Methode namens forceListRefreshOn () in meinem Button-Handler neu zeichnen und den ausgewählten Standard korrekt anzeigen lassen:

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

Die Hilfsmethode tauscht einfach die ObservableList aus der ListView aus und dann wieder ein, wodurch die ListView vermutlich gezwungen wird, ihre ListCells zu aktualisieren:

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

Ich bin mir nicht sicher, ob dies in JavaFX 2.2 funktioniert, aber in JavaFX 8. Ich habe eine Weile gebraucht, um es herauszufinden. Sie müssen Ihre eigene ListViewSkin erstellen und eine refresh-Methode wie die folgende hinzufügen:

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

Dadurch wird updateItem aufgerufen, ohne dass die gesamte Observable-Auflistung ersetzt werden muss.

Um die neue Skin zu verwenden, müssen Sie sie instanziieren und in der initialize -Methode des Controllers festlegen, falls Sie FXML verwenden:

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

Ich fand das, nachdem ich das Verhalten eines Akkordeons getestet hatte. Mit diesen Steuerelementen wird die darin enthaltene ListView jedes Mal aktualisiert, wenn Sie sie erweitern.

12
Eldelshell

Für jede andere Stelle, die auf dieser Seite endet:

Der richtige Weg, dies zu tun, besteht darin, Ihre Beobachtungsliste mit einem "Extraktor" -Callback zu versehen. Dies signalisiert der Liste (und ListView) alle Eigenschaftsänderungen.

Anzuzeigende benutzerdefinierte Klasse:

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

Und dein Haupttextkörper:

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!!!

Siehe (und ähnliche Methoden): https://docs.Oracle.com/javafx/2/api/javafx/collections/FXCollections.html#observableArrayList(javafx.util.Callback)

11
Andrey

Ich bin nicht sicher, ob ich etwas vermisse, aber zuerst habe ich Scottbs Lösung ausprobiert, dann habe ich herausgefunden, dass es eine bereits implementierte refresh () -Methode gibt, die den Trick für mich gemacht hat.

ListView<T> list;
list.refresh();
1
Dom

Anscheinend müssen Sie die updateItem-Methode nicht verwenden. Was Sie brauchen, ist das Speichern des Indikators der aktuell voreingestellten Zelle (die jetzt in den Benutzerdaten enthalten ist) in OblectProperty. Fügen Sie aus jeder Zelle einen Listener für diese Eigenschaft hinzu. Wenn sich der Wert ändert, weisen Sie einen Stil neu zu.

Aber ich denke, eine solche Bindung wird ein weiteres Problem darstellen - während des Bildlaufs werden neue Zellen erstellt, aber die alten verlassen die Bindung nicht, was zu einem Speicherverlust führen kann. Sie müssen also einen Listener hinzufügen, um Zellen aus der Szene zu entfernen. Ich denke, es kann durch Hinzufügen eines Listeners auf der parentProperty getan werden, wenn es null wird - Bindung entfernen.

Die Aktualisierung der Benutzeroberfläche sollte nicht erzwungen werden. Es wird automatisch aktualisiert, wenn Eigenschaften/Rendering bestehender Knoten geändert werden. Sie müssen also nur das Erscheinungsbild/die Eigenschaft vorhandener Knoten (Zellen) aktualisieren. Und nicht zu vergessen, dass Zellen massiv erzeugt werden können, während gescrollt/neu gerendert wird usw.

0
Alexander Kirov