JavaFX TableView mit CheckBox als Boolean

mathiasj

Bekanntes Mitglied
Hallo,
ich versuche jetzt schon seit einiger Zeit in meiner TableView die booleans aus meinem Model als CheckBoxen darzustellen. Das hier ist mein Model:

Java:
package net.softwarepage.voclearn.code;

import java.io.Serializable;

public class Card implements Serializable{
	
	private static final long serialVersionUID = 2526051651510336406L;
	private String question;
	private String anwser;
	private boolean doppelt;
	
	public Card(String question_, String anwser_, boolean doppelt_) {
		question = question_;
		anwser = anwser_;
		doppelt = doppelt_;
	}
	
	public void setQuestion(String question_) {
		question = question_;
	}
	
	public void setAnwser(String anwser_) {
		anwser = anwser_;
	}
	
	public void setDoppelt(boolean doppelt_) {
		doppelt = doppelt_;
	}
	
	public String getQuestion() {
		return question;
	}
	
	public String getAnwser() {
		return anwser;
	}
	
	public boolean isDoppelt() {
		return doppelt;
	}
	
	public Card invert() {
		return new Card(getAnwser(), getQuestion(), isDoppelt());
	}
}

Hier tritt auch schon das erste Problem auf: Beim googlen bin ich meistens nur auf Ansätze gestoßen, in denen BooleanProperties verwendet werden, das kann ich aber nicht machen, da diese nicht serialisierbar sind, ich diese Klasse aber abspeichern muss. Meine Tabelle erstelle ich so:

Java:
doppelt.setCellFactory(CheckBoxTableCell.<Card>forTableCell(doppelt));

Wenn ich jetzt aber eine Datei lade, ist die CheckBox immer nicht selected.
Ich habe auch so etwas probiert:

Java:
doppelt.setCellFactory(c -> new MyCheckBoxTableCell<>());

public static class MyCheckBoxTableCell<S, T> extends TableCell<S, T> {

        private final CheckBox checkBox;

        private ObservableValue<T> ov;

 

        public MyCheckBoxTableCell() {

            this.checkBox = new CheckBox();

            this.checkBox.setAlignment(Pos.CENTER);

 

            setAlignment(Pos.CENTER);

            setGraphic(checkBox);

        } 

         

        @Override 
        public void updateItem(T item, boolean empty) {

            super.updateItem(item, empty);

            if (empty) {

                setText(null);

                setGraphic(null);

            } else {

                setGraphic(checkBox);

                if (ov instanceof BooleanProperty) {

                    checkBox.selectedProperty().unbindBidirectional((BooleanProperty) ov);

                }

                ov = getTableColumn().getCellObservableValue(getIndex());

                if (ov instanceof BooleanProperty) {

                    checkBox.selectedProperty().bindBidirectional((BooleanProperty) ov);

                }

            }

        }

    }

Dieser Entwurf hat auch nicht funktioniert.

Außerdem weiß ich auch noch nicht, wie ich die CheckBox dann letztendlich mit dem Model verknüpfen kann, also das beides richtig synchronisiert ist. Füge ich dafür ein onEdit bei der Column ein, oder mache ich das anders? (Es gibt ja auch noch ein bindBidirectional() (wie ich es im letzten Code Beispiel verwendet habe), aber ich glaube das funktioniert nur mit BooleanProperties...).

Ich habe auch schon versucht so etwas zu machen:

Java:
package net.softwarepage.voclearn.code;

import java.io.Serializable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;

public class Card implements Serializable{
	
	private static final long serialVersionUID = 2526051651510336406L;
	private String question;
	private String anwser;
	private transient BooleanProperty doppelt;
        private boolean doppeltBoolean;
	
	public Card(String question_, String anwser_, boolean doppelt_) {
		question = question_;
		anwser = anwser_;
		doppeltBoolean = doppelt_;
                doppelt = new SimpleBooleanProperty(doppelt_);
	}
	
	public void setQuestion(String question_) {
		question = question_;
	}
	
	public void setAnwser(String anwser_) {
		anwser = anwser_;
	}
	
	public void setDoppelt(boolean doppelt_) {
		doppeltBoolean = doppelt_;
                doppelt = new SimpleBooleanProperty(doppelt_);
	}
	
	public String getQuestion() {
		return question;
	}
	
	public String getAnwser() {
		return anwser;
	}
	
	public boolean isDoppelt() {
		return doppeltBoolean;
	}
        
        public BooleanProperty doppeltProperty() {return doppelt;}
	
	public Card invert() {
		return new Card(getAnwser(), getQuestion(), isDoppelt());
	}
}

Aber damit hab ich das ganze auch nicht zum Laufen gebracht.

Ich hoffe ihr könnt mir helfen, bin echt langsam am verzweifeln!
 
Ich versuche gerade durch deine Gedankengänge durchzusteigen, aber ich hab wohl tatsächlich zu wenig Schlaf abbekommen heute Nacht: Ich schaffe es nicht!

Fangen wir von vorne an. Du *musst* dein Modell zwingend serialisierbar machen? Wieso? (Entschuldige die doofe Frage, aber in meinen ganzen Jahren als Java-Entwickler habe ich dieses Feature nie verwenden müssen und wenn, dann XML oder JSON verwendet.)
Das Observables wie BooleanProperties allerdings nicht serialialisierbar sind, wusste ich nicht. Sei's drum: Ich verstehe deine erweiterte Card-Klasse nicht. Warum verwendest du boolean und BooleanProperty nebeneinander? Warum überschreibst du bei jedem setDoppelt die BooleanProperty, statt sie nur neu zu setzen?

Warum dieses "ov" in der TableCell? Warum nicht ((Card)item)doppeltProperty() - du musst es nicht in der Cell noch einmal abspeichern. Auch die Cell würde ich einfach vor Ort machen ([c]CheckBox cb=new CheckBox(); cb.selectedProperty().bind...;setGraphic(cb);[/c] - aber das ist eher Geschmackssache).

BTW: Warum überhaupt Denglische Bezeichner für alles, da graust es mir ja! 😛
 
Wenn keine FX-Propertys zur Verfügung stehen, muss man einiges manuell dazuprogrammieren. Ich würde es wie folgt probieren:

Java:
    public void start(Stage stage) 
    throws Exception
    {
        VBox pane = new VBox();
        TableView<Card> tv = new TableView<>();
        tv.setEditable(true);
        tv.getItems().add(new Card("?", "Fu", true));
        tv.getItems().add(new Card("?", "Bar", false));
        
        TableColumn<Card, Boolean> doppelt = new TableColumn<>("doppelt");
        doppelt.setEditable(true);
        doppelt.setCellValueFactory(new PropertyValueFactory<>("doppelt"));
        doppelt.setCellFactory( tc -> {
            return new TableCell<Card,Boolean>()
            {
                CheckBox checkBox = new CheckBox();  
                CardChangeListener changeListener = new CardChangeListener();
                
                {
                    setAlignment(Pos.CENTER);
                }
                
                protected void updateItem(Boolean item, boolean empty)
                {
                    super.updateItem(item, empty);
                    if (!empty)
                    {
                        setGraphic(checkBox);
                        checkBox.selectedProperty().removeListener(changeListener);
                        Card card = getTableView().getItems().get(getIndex());
                        checkBox.setSelected(card.isDoppelt());
                        changeListener.setCard(card);
                        checkBox.selectedProperty().addListener(changeListener);
                    }
                    else
                    {
                         setGraphic(null);
                    }
                }
            };
        } );
        tv.getColumns().add(doppelt);
        
        
        pane.getChildren().add(tv);
        stage.setScene(new Scene(pane));
        stage.show();
    }


    public static class CardChangeListener
    implements ChangeListener<Boolean>
    {
        private Card card;
        
        public void setCard(Card c)
        {
            this.card = c;
        }
        
        public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
        {
            if (card != null)
                card.setDoppelt(newValue);                             
        }
    }
 
Zuletzt bearbeitet:
@dzim
Ja, die Serialisierbarkeit würde ich gerne behalten, da ich es genauso in früheren Versionen vom Programm gehandhabt habe, und ich will, dass auch alte abgespeicherte Dateien noch geladen werden können. Ich habe dann versucht ein BooleanProperty zu machen, das ich aber nicht abspeichere, sondern einfach mit dem boolean in Synchronisierung halte; dass ich sie neu setze war natürlich ein Fehler. Und die bezeichner finde ich selbst schlimm 🙂 Der Code ist schon älter (aus meinen Java Anfängen) und ist noch nicht in den Genuss eines ordentlichen Refactoring gekommen ^^

Die Lösung, die von @Felli vorgeschlagen wurde, funktioniert perfekt! Danke! Nur eine Stelle im Code verstehe ich nicht:

Java:
return new TableCell<Card,Boolean>()
            {
                CheckBox checkBox = new CheckBox();  
                CardChangeListener changeListener = new CardChangeListener();
                
                {
                    setAlignment(Pos.CENTER);  //Ist das hier der Konstruktor?  
                }
                ...

Nach den Instanzvariablen wird einfach eine spitze Klammer geöffnet, ohne dass eine Methode oder sonst was deklariert wird. Ist das dann der Konstrukor einer anonymen inneren Klasse?

Danke auf jeden Fall!
 

Zurück
Oben