отредактировано: ОК, я думаю, что в основном решил свою собственную проблему, или, по крайней мере, у меня есть кое-что, что работает, но это кажется довольно хакерским и небезопасным ... Я просто добавил немного логики c в TilePane, чтобы заставить его делать то, что я хочу, но это много пользовательского кода. Не уверен, что это был правильный способ сделать это, но он работает ... Публикация моего решения на случай, если у кого-то возникнут отзывы или подобные вопросы в будущем. Это нормально?
В основном мне интересно, нормально ли мне приводить ноды в CardViews, когда я щелкаю их внутри TilePanes? Могу ли я сделать это лучше с ListView? И есть ли способ сделать это с другим компонентом, который будет создавать минимум узлов вне экрана?
Оригинал: я работаю над карточной игрой JavaFX, и мне было интересно, как я могу получить безопасный TilePane, если это имеет смысл. Прямо сейчас я работаю над составлением колоды в игре. Я хочу отображать карты, сгенерированные из списка карт, и хочу отображать их в строках по 3, если это возможно. Я хочу макет TilePane, но безопасность типов ListView.
ListView безопасен для типов, но его элементы могут отображаться только в виде списка (по горизонтали или вертикали), тогда как TilePane дает мне точно макет, который я хочу, но не типобезопасный.
Можно ли как-то получить оба? Должен ли я просто создать собственный компонент?
Вот (более полный) Минимальный воспроизводимый пример.
DeckBuilderControllerMRE
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.TilePane;
import javafx.scene.layout.VBox;
import java.util.ArrayList;
import java.util.HashMap;
public class DeckBuilderControllerMRE {
@FXML TilePane availableCards, cardsInDeck;
//maps from a list of available cards to the number of those cards in the deck
ObservableMap<Card, Integer> currentDeckMap = FXCollections.observableMap(new HashMap<>());
//these map from card data objects to view nodes, which is wasteful but works...
HashMap<Card, CardView> availableViewsMap = new HashMap<>(), deckViewsMap = new HashMap<>();
public void initialize() {
//dummy card data
ArrayList<Card> allCards = new ArrayList<>();
allCards.add(new Card("sword guy", 5));
allCards.add(new Card("arrow man", 3));
allCards.add(new Card("shablagoo", 10000000));
allCards.add(new Card("guy # 4", 4));
allCards.add(new Card("Dr. Evil", 7));
allCards.add(new Card("one more", -5));
//this maps cards to total avaialble, because you're only allowed to have a certain number of each card
HashMap<Card, Integer> cardQuantities = new HashMap<>();
for(int i = 0; i < allCards.size(); i++)
cardQuantities.put(allCards.get(i), i);
//instantiate card views and add to view maps
//also sets up binding for quantity labels
for(Card card : allCards){
currentDeckMap.put(card, 0);
CardView availableView = card.getCardView();
availableView.quantityLabel.textProperty().bind(Bindings.concat("Quantity Available: ",
Bindings.subtract(cardQuantities.get(card), Bindings.integerValueAt(currentDeckMap, card))));
availableViewsMap.put(card, availableView);
availableCards.getChildren().add(availableView);
CardView deckView = card.getCardView();
deckView.quantityLabel.textProperty().bind(Bindings.concat("Quantity in deck: ", Bindings.integerValueAt(currentDeckMap, card)));
deckViewsMap.put(card, deckView);
}
//clicking available cards adds them to your deck
availableCards.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
//check if I clicked the container instead of a card
if(event.getTarget() == availableCards)
return;
//otherwise I bubble up to the container node CardView
Node node = (Node) event.getTarget();
while(node.getParent() != availableCards)
node = node.getParent();
//and then I cast the nodes to CardViews
//which is the part I think might need fixing
CardView selectedView = (CardView) node;
Card selectedCard = selectedView.card;
//and then I update the map which updates the quantityLabel
currentDeckMap.put(selectedCard, currentDeckMap.get(selectedCard) + 1);
event.consume();
});
//and this does the same thing but removes them
cardsInDeck.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
if(event.getTarget() == cardsInDeck)
return;
Node node = (Node) event.getTarget();
while(node.getParent() != cardsInDeck)
node = node.getParent();
CardView selectedView = (CardView) node;
Card selectedCard = selectedView.card;
currentDeckMap.put(selectedCard, currentDeckMap.get(selectedCard) - 1);
event.consume();
});
currentDeckMap.addListener((MapChangeListener<Card, Integer>) change -> {
Card card = change.getKey();
int stillAvailable = cardQuantities.get(card) - currentDeckMap.get(card);
if(stillAvailable == 0)
availableCards.getChildren().remove(availableViewsMap.get(card));
if(stillAvailable == 1 && !availableCards.getChildren().contains(availableViewsMap.get(card)))
availableCards.getChildren().add(availableViewsMap.get(card));
if(currentDeckMap.get(card) == 0)
cardsInDeck.getChildren().remove(deckViewsMap.get(card));
else if(currentDeckMap.get(card) == 1 && !cardsInDeck.getChildren().contains(deckViewsMap.get(card)))
cardsInDeck.getChildren().add(deckViewsMap.get(card));
});
}
class Card {
String cardName;
int cardStrength;
Card(String n, int s){
cardName = n;
cardStrength = s;
}
public CardView getCardView(){
return new CardView(this);
}
}
//cards usually have images and stuff, but this is enough to see what I'm doing
//also, CardView extends some kind of container node, currently VBox, but that might change later
class CardView extends VBox {
Card card;
Label nameLabel, strengthLabel, quantityLabel;
CardView(Card card){
this.card = card;
nameLabel = new Label(card.cardName);
strengthLabel = new Label(card.cardStrength + "");
quantityLabel = new Label();
this.getChildren().addAll(nameLabel, strengthLabel, quantityLabel);
}
}
}
f xml выглядит следующим образом ( это пограничная панель с TilePanes, обернутыми в ScrollPanes слева и справа). (Вам может понадобиться добавить идентификатор пакета в атрибут fx: controller. Все остальное должно работать, хотя).
deck_builder.f xml
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="900.0" prefWidth="1300.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="DeckBuilderControllerMRE">
<left>
<ScrollPane hbarPolicy="NEVER" minHeight="-Infinity" minViewportHeight="200.0" minViewportWidth="400.0" minWidth="-Infinity" prefHeight="200.0" prefViewportHeight="200.0" prefViewportWidth="400.0" prefWidth="400.0" BorderPane.alignment="CENTER">
<content>
<TilePane fx:id="availableCards" hgap="10.0" prefColumns="3" tileAlignment="TOP_LEFT" vgap="10.0" />
</content>
<BorderPane.margin>
<Insets />
</BorderPane.margin>
</ScrollPane>
</left>
<right>
<ScrollPane hbarPolicy="NEVER" minHeight="-Infinity" minViewportHeight="200.0" minViewportWidth="400.0" minWidth="-Infinity" prefHeight="200.0" prefViewportHeight="200.0" prefViewportWidth="400.0" prefWidth="400.0" BorderPane.alignment="CENTER">
<content>
<TilePane fx:id="cardsInDeck" hgap="10.0" prefColumns="3" tileAlignment="TOP_LEFT" vgap="10.0" />
</content>
<BorderPane.margin>
<Insets />
</BorderPane.margin>
</ScrollPane>
</right>
</BorderPane>
, и это мой основной метод (в основном просто загружает и отображает f xml)
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args){
launch(args)
}
public void start(Stage primaryStage) throws Exception {
window = primaryStage;
window.setTitle("Card Game");
window.setScene(new Scene(FXMLLoader.load(getClass().getResource("layouts/deck_builder.fxml")), sceneWidth, sceneHeight));
window.show();
}
}
Это моя первая публикация на Stack Overflow, поэтому любые отзывы о том, как я могу улучшить эту вещь, приветствуются, хотя я добрый попыток практиковать быстрое развитие здесь, поэтому незначительные оптимизации на самом деле не являются приоритетом.
Спасибо за вашу помощь / время.