JavaFX列表视图元素的奇怪行为

huangapple go评论127阅读模式
英文:

Weird behaviour in JavaFX list view elements

问题

我有两个列表视图,右边的一个有三个“卡片”(名称和值),每个卡片都有一个按钮,点击该按钮将该卡片添加到中心列表。我还可以点击中心列表上该卡片的按钮来移除它。我的问题是,如果我向中心列表添加两个或更多卡片,然后尝试移除它们,界面会变得奇怪,无法删除它们或者只删除一个或两个。

public class DeckManagement implements Initializable {
    private final SceneHandler sceneHandler = SceneHandler.getInstance();
    @FXML
    private ListView<Card> rightList;

    @FXML
    private ListView<Card> centerList;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        rightList.setCellFactory(param -> new ListCell<Card>() {
            @Override
            protected void updateItem(Card card, boolean empty) {
                super.updateItem(card, empty);
                if (empty || card == null) {
                    setText(null);
                } else {
                    VBox container = new VBox();
                    Button cardButton = new Button("Add");
                    cardButton.setOnAction(event -> {
                        if (!card.isMoved()) {
                            centerList.getItems().add(card);
                            card.setMoved(true);
                        }
                    });
                    container.getChildren().addAll(new javafx.scene.control.Label(card.toString()), cardButton);
                    setGraphic(container);
                }
            }
        });
        rightList.getItems().addAll(
            new Card("Card 1", 10),
            new Card("Card 2", 5),
            new Card("Card 3", 8)
        );
        centerList.setCellFactory(param -> new ListCell<Card>() {
            @Override
            protected void updateItem(Card card, boolean empty) {
                super.updateItem(card, empty);
                if (empty || card == null) {
                    setText(null);
                } else {
                    VBox container = new VBox();
                    Button cardButton = new Button("Remove");
                    cardButton.setOnAction(event -> {
                        card.setMoved(false);
                        centerList.getItems().remove(card);
                    });
                    container.getChildren().addAll(new javafx.scene.control.Label(card.toString()), cardButton);
                    setGraphic(container);
                }
            }
        });
    }
}
英文:

I have 2 list views, on the right one I have three "cards" (name and a value), each with a button which when clicked adds that card to the centerlist. I also can click on the same button of that card on the centerlist to remove that. My problem is if I add two or more card to the centerList and I proceed to remove them, the interface goes weird not deleting them or only one or two.

public class DeckManagement implements Initializable {
    private final SceneHandler sceneHandler = SceneHandler.getInstance();
@FXML
    private ListView&lt;Card&gt; rightList;

    @FXML
    private ListView&lt;Card&gt; centerList;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        rightList.setCellFactory(param -&gt; new ListCell&lt;Card&gt;() {
            @Override
            protected void updateItem(Card card, boolean empty) {
                super.updateItem(card, empty);
                if (empty || card == null) {
                    setText(null);
                } else {
                    VBox container = new VBox();
                    Button cardButton = new Button(&quot;Add&quot;);
                    cardButton.setOnAction(event -&gt; {
                        if (!card.isMoved()) {
                            centerList.getItems().add(card);
                            //rightList.getItems().remove(card);
                            card.setMoved(true);
                        }
                    });
                    container.getChildren().addAll(new javafx.scene.control.Label(card.toString()), cardButton);
                    setGraphic(container);
                }
            }
        });
        rightList.getItems().addAll(
            new Card(&quot;Card 1&quot;, 10),
            new Card(&quot;Card 2&quot;, 5),
            new Card(&quot;Card 3&quot;, 8)
        );
        centerList.setCellFactory(param -&gt; new ListCell&lt;Card&gt;() {
            @Override
            protected void updateItem(Card card, boolean empty) {
                super.updateItem(card, empty);
                if (empty || card == null) {
                    setText(null);
                } else {
                    VBox container = new VBox();
                    Button cardButton = new Button(&quot;Remove&quot;);
                    cardButton.setOnAction(event -&gt; {
                        card.setMoved(false);
                        centerList.getItems().remove(card);
                    });
                    container.getChildren().addAll(new javafx.scene.control.Label(card.toString()), cardButton);
                    setGraphic(container);
                }
            }
        });
    }
}

答案1

得分: 2

以下是您要翻译的内容:

If your list cell becomes non-empty, it's updateItem(...) method will be called with a non-null Card and a value of false for empty. In this case you set the graphic for that cell to the VBox.

If, subsequently, the Card instance displayed in that cell is removed from the ListView and the cell becomes empty, then updateItem(null, true) will be called. However your updateItem(...) implementation in this case does not remove the graphic, so the graphic remains displayed.

Here is an implementation that should work. I refactored this to avoid repeatedly creating new controls (which is extremely inefficient) and also to remove unnecessary repetitive code.

public class DeckManagement implements Initializable {
private final SceneHandler sceneHandler = SceneHandler.getInstance();
@FXML
private ListView rightList;

@FXML
private ListView centerList;

private static class DeckCell extends ListCell {

private final VBox container;
private final Button cardButton ;
private final Label label;

DeckCell(String buttonText) {
label = new Label();
cardButton = new Button(buttonText);
container = new VBox(label, cardButton);

cardButton.setOnAction(e -> {
Card card = getItem();
if (card.isMoved()) {
centerList.getItems().remove(card);
} else {
centerList.getItems().add(card);
// rightList.getItems().remove(card);
}
card.setMoved(! card.isMoved());
});
}

@Override
protected void updateItem(Card card, boolean empty) {
super.updateItem(card, empty);
if (empty || card == null) {
setGraphic(null);
} else {
label.setText(card.toString());
setGraphic(container);
}
}
}

@Override
public void initialize(URL location, ResourceBundle resources) {
rightList.setCellFactory(param -> new DeckCell("Add"));
rightList.getItems().addAll(
new Card("Card 1", 10),
new Card("Card 2", 5),
new Card("Card 3", 8)
);
centerList.setCellFactory(param -> new DeckCell("Remove"));
}
}

Note that if you implement your Card class using a JavaFX property to represent "moved", your action handler can just update the property and the lists can take care of themselves:

public class Card {

private final BooleanProperty moved = new SimpleBooleanProperty(false);

public BooleanProperty movedProperty() {
return moved ;
}

public final boolean isMoved() {
return movedProperty().get();
}

public final void setMoved(boolean moved) {
movedProperty().set(moved);
}

private final String name ;
private final int value ;

public Card(String name, int value) {
this.name = name ;
this.value = value ;
}

public String getName() {
return name ;
}

public int getValue() {
return value;
}
}

then

public class DeckManagement implements Initializable {
private final SceneHandler sceneHandler = SceneHandler.getInstance();
@FXML
private ListView rightList;

@FXML
private ListView centerList;

private static class DeckCell extends ListCell {

private final VBox container;
private final Button cardButton ;
private final Label label;

private final boolean movedValue;

DeckCell(String buttonText, boolean movedValue) {
this.movedValue = movedValue;

label = new Label();
cardButton = new Button(buttonText);
container = new VBox(label, cardButton);

cardButton.setOnAction(e -> {
Card card = getItem();
card.setMoved(movedValue);
});
}

@Override
protected void updateItem(Card card, boolean empty) {
super.updateItem(card, empty);
if (empty || card == null) {
setGraphic(null);
} else {
label.setText(card.toString());
setGraphic(container);
}
}
}

@Override
public void initialize(URL location, ResourceBundle resources) {
ObservableList allCards = FXCollections.observableArrayList(card -> new Observable[] { card.movedProperty() });
rightList.setItems(allCards);
rightList.setCellFactory(param -> new DeckCell("Add", true));

allCards.addAll(
new Card("Card 1", 10),
new Card("Card 2", 5),
new Card("Card 3", 8)
);

// List that fires updates if any moved properties change:
centerList.setItems(new FilteredList<>(allCards, card::isMoved));
centerList.setCellFactory(param -> new DeckCell("Remove", false));
}
}

英文:

If your list cell becomes non-empty, it's updateItem(...) method will be called with a non-null Card and a value of false for empty. In this case you set the graphic for that cell to the VBox.

If, subsequently, the Card instance displayed in that cell is removed from the ListView and the cell becomes empty, then updateItem(null, true) will be called. However your updateItem(...) implementation in this case does not remove the graphic, so the graphic remains displayed.

Here is an implementation that should work. I refactored this to avoid repeatedly creating new controls (which is extremely inefficient) and also to remove unnecessary repetitive code.

public class DeckManagement implements Initializable {
private final SceneHandler sceneHandler = SceneHandler.getInstance();
@FXML
private ListView&lt;Card&gt; rightList;
@FXML
private ListView&lt;Card&gt; centerList;
private static class DeckCell extends ListCell&lt;Card&gt; {
private final VBox container;
private final Button cardButton ;
private final Label label;
DeckCell(String buttonText) {
label = new Label();
cardButton = new Button(buttonText);
container = new VBox(label, cardButton);
cardButton.setOnAction(e -&gt; {
Card card = getItem();
if (card.isMoved()) {
centerList.getItems().remove(card);
} else {
centerList.getItems().add(card);
// rightList.getItems().remove(card);
}
card.setMoved(! card.isMoved());
});
}
@Override
protected void updateItem(Card card, boolean empty) {
super.updateItem(card, empty);
if (empty || card == null) {
setGraphic(null);
} else {
label.setText(card.toString());
setGraphic(container);
}
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
rightList.setCellFactory(param -&gt; new DeckCell(&quot;Add&quot;)); 
rightList.getItems().addAll(
new Card(&quot;Card 1&quot;, 10),
new Card(&quot;Card 2&quot;, 5),
new Card(&quot;Card 3&quot;, 8)
);
centerList.setCellFactory(param -&gt; new DeckCell(&quot;Remove&quot;));
}
}

Note that if you implement your Card class using a JavaFX property to represent "moved", your action handler can just update the property and the lists can take care of themselves:

public class Card {
private final BooleanProperty moved = new SimpleBooleanProperty(false);
public BooleanProperty movedProperty() {
return moved ;
}
public final boolean isMoved() {
return movedProperty().get();
}
public final void setMoved(boolean moved) {
movedProperty().set(moved);
}
private final String name ;
private final int value ;
public Card(String name, int value) {
this.name = name ;
this.value = value ;
}
public String getName() {
return name ;
}
public int getValue() {
return value;
}
}

then

public class DeckManagement implements Initializable {
private final SceneHandler sceneHandler = SceneHandler.getInstance();
@FXML
private ListView&lt;Card&gt; rightList;
@FXML
private ListView&lt;Card&gt; centerList;
private static class DeckCell extends ListCell&lt;Card&gt; {
private final VBox container;
private final Button cardButton ;
private final Label label;
private final boolean movedValue;
DeckCell(String buttonText, boolean movedValue) {
this.movedValue = movedValue;
label = new Label();
cardButton = new Button(buttonText);
container = new VBox(label, cardButton);
cardButton.setOnAction(e -&gt; {
Card card = getItem();
card.setMoved(movedValue);
});
}
@Override
protected void updateItem(Card card, boolean empty) {
super.updateItem(card, empty);
if (empty || card == null) {
setGraphic(null);
} else {
label.setText(card.toString());
setGraphic(container);
}
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
ObservableList&lt;Card&gt; allCards = FXCollections.observableArrayList(card -&gt; new Observable[] { card.movedProperty() });
rightList.setItems(allCards);
rightList.setCellFactory(param -&gt; new DeckCell(&quot;Add&quot;, true)); 
allCards.addAll(
new Card(&quot;Card 1&quot;, 10),
new Card(&quot;Card 2&quot;, 5),
new Card(&quot;Card 3&quot;, 8)
);
// List that fires updates if any moved properties change:
centerList.setItems(new FilteredList&lt;&gt;(allCards, card::isMoved));
centerList.setCellFactory(param -&gt; new DeckCell(&quot;Remove&quot;, false));
}
}

huangapple
  • 本文由 发表于 2023年4月6日 22:47:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/75950862.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定