Search code examples
javajavafxmvvmimageviewbind

how to bind imageview to array index mvvm javafx


I'm currently trying to program BlackJack in JavaFX and I use ImageViews to display the cards. I have a player model which has a LinkedHashMap of Strings and cards from my Card class.

LinkedHashMap<String, Card> cards = new LinkedHashMap<>();

The String always looks like "ACE-CLUB" or "SIX-HEART", because my imported images are also named like that ("ACE-CLUB.png").

Now, I don't know how I should bind the ImageView playerCardOne to the first position of the LinkedHashMap. I thought about making an array which only stores the Strings of the LinkedHashMap but then I'd still don't know how I bind an ImageView. I thought it may look something like this, but it doesn't work:

playerCardOne.imageProperty().bind(new Image("/cards/" + vm.getPlayerCard(0) + ".png"));

The method getPlayerCard(0) returns the String which is stored at the index of the number in the brackets (0).

I hope someone can help? It's my first time using MVVM and making a more complex program using JavaFX. Thank you.


Solution

  • Answering your question, you need to make cards observable first:

    ObservableMap<String, Card> cards = FXCollections.observableMap(new LinkedHashMap<>());
    

    Then you can create a Binding that always returns the first item in your map like this:

    ObjectBinding<Image> firstImageBinding = Bindings.createObjectBinding(() -> {
        Iterator<Map.Entry<String, Card>> iterator = cards.entrySet().iterator();
        if (iterator.hasNext()) {
            return new Image("/cards/" + iterator.next().getKey() + ".png");
        }
        return null;
    }, cards);
    

    Finally, you can bind it to an Image property:

    playerCardOne.imageProperty().bind(firstImageBinding);
    

    Although this might work as you expect, I believe you can make it better.

    First, I don't know how you implemented Card class, but anytime you need a fixed set of constants, you should use enums:

    Suit class:

    enum Suit { 
        CLUB, DIAMOND, HEART, SPADE 
    }
    

    Rank class:

    enum Rank { 
        ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING 
    }
    

    Card class:

    public class Card {
        private final Suit suit;
        private final Rank rank;
    
        public Card(Suit suit, Rank rank) {
            this.suit = suit;
            this.rank = rank;
        }
    
        public Suit getSuit() {
            return suit;
        }
    
        public Rank getRank() {
            return rank;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Card card = (Card) o;
            return suit == card.suit &&
                    rank == card.rank;
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(suit, rank);
        }
    
        @Override
        public String toString() {
            return String.format("%s %s", suit, rank);
        }
    }
    

    Now, you can get an Image for a Card without the need to use a Map<String, Card>:

    public Image getImageForCard(Card card) {
        String url = String.format("/cards/%s-%s.png", card.getRank(), card.getSuit());
        return new Image(url);
    }
    

    If you are always interested in the image of the first card in a List<Card>, you can get it like this:

    ObservableList<Card> cards = FXCollections.observableArrayList();
    
    ImageView playerCardOne = new ImageView();
    ObjectBinding<Image> firstImageBinding = Bindings.createObjectBinding(() -> 
            cards.isEmpty() ? null : getImageForCard(cards.get(0)), cards);
    playerCardOne.imageProperty().bind(firstImageBinding);
    
    cards.add(new Card(Suit.CLUB, Rank.ACE));
    // playerCardOne's image is now /cards/ACE-CLUB.png
    
    cards.add(0, new Card(Suit.HEART, Rank.SIX));
    // playerCardOne's image is now /cards/SIX-HEART.png