javafx how to disable one item in checkboxTreeItem?

4.6k Views Asked by At

I am new to JavaFX and I am having a problem in checkbox Tree View.
I want to disable one item inside root value **Serial No but as per JavaFX documentation, it seems that it is not possible.

I have disabled click effect cbTreeViewdisable(treeItems, 3)
Is there any way to grey out any one value (or set an id) so that user knows it is disabled.

private TreeView<String> cbTreeView;    
@Override
public void start(Stage primaryStage) {
 StackPane root = new StackPane();
 cbTreeView = new TreeView();
 CheckboxTreeItem<String> rootItem = new CheckboxTreeItem("Serial No.");
 final CheckboxTreeItem[] treeItems = new CheckboxTreeItem[6];
 for (int i = 0; i < 6; i++) {
   treeItems[i] = new CheckboxTreeItem(""+i+"");
   rootItem.getChildren().addAll(treeItems[i]);  
 }

 root.setExpanded(true);
 CheckboxTreeItem rootItem2 = new CheckboxTreeItem("child1");
 final CheckboxTreeItem[] treeItems2 = new CheckboxTreeItem[6];

 for (int i = 0; i < 6; i++) {
   treeItems2[i] = new CheckboxTreeItem(""+i+"");
   rootItem2.getChildren().addAll(treeItems2[i]);  
 }

 cbTreeView.setRoot(rootItem);
 cbTreeView.setCellFactory(CheckboxTreeCell.<String>forTreeView());
 rootItem.getChildren().set(2,rootItem2);
 cbTreeViewdisable(treeItems, 3);
 //rest of code imports css and draws stage which is not relevant to my question
 }

 private void cbTreeViewdisable(final CheckboxTreeItem[] treeItems, final int id) {
    treeItems[id].setIndependent(Boolean.TRUE);
    treeItems[id].setValue(treeItems[id].getValue().toString()+" Note: you can't select this!)");
    treeItems[id].selectedProperty().addListener(new ChangeListener<Boolean>() {

    @Override
    public void changed(ObservableValue<? extends Boolean> observable, 
        Boolean oldvalue, Boolean newvalue) {
        Platform.runLater(new Runnable() {
        @Override
        public void run() {
            treeItems[id].setSelected(Boolean.FALSE);       
   } 
4

There are 4 best solutions below

2
On BEST ANSWER

One way to allow disabling single items of the tree is binding their cell disable property to an external property you can modify on runtime. For that, when we set the cell factory, we can add this binding to the cell implementation. But we need a reference, so we'll use the value of the item, for the sake of simplicity (this requires unique values).

Let's use this pojo:

class Wrap {

    public Wrap(String name){
        this.name.set(name);
        this.disabled.set(false);
    }

    private final StringProperty name = new SimpleStringProperty();

    public String getName() {
        return name.get();
    }

    public void setName(String value) {
        name.set(value);
    }

    public StringProperty nameProperty() {
        return name;
    }
    private final BooleanProperty disabled = new SimpleBooleanProperty();

    public boolean isDisabled() {
        return disabled.get();
    }

    public void setDisabled(boolean value) {
        disabled.set(value);
    }

    public BooleanProperty disabledProperty() {
        return disabled;
    }
}

so we can have one collection for all of the items on the tree view:

private TreeView<String> cbTreeView; 
private final List<Wrap> disableList = new ArrayList<>();

@Override
public void start(Stage primaryStage) {
    cbTreeView = new TreeView();

    CheckBoxTreeItem<String> rootItem = new CheckBoxTreeItem("Serial No.");
    final List<CheckBoxTreeItem<String>> treeItems = new ArrayList<>(6);
    for (int i = 0; i < 6; i++) {
        CheckBoxTreeItem<String> item = new CheckBoxTreeItem("0"+i+"");
        item.setIndependent(true);
        treeItems.add(item);  
        disableList.add(new Wrap("0"+i+""));
    }
    rootItem.getChildren().addAll(treeItems);

    rootItem.setExpanded(true);
    rootItem.setIndependent(true);
    CheckBoxTreeItem<String> rootItem2 = new CheckBoxTreeItem("child1");
    final List<CheckBoxTreeItem<String>> treeItems2 = new ArrayList<>(6);
    for (int i = 0; i < 6; i++) {
        CheckBoxTreeItem<String> item = new CheckBoxTreeItem("1"+i+"");
        item.setIndependent(true);
        treeItems2.add(item); 
        disableList.add(new Wrap("1"+i+""));
    }
    rootItem2.getChildren().addAll(treeItems2);
    rootItem2.setIndependent(true);
    rootItem.getChildren().set(2,rootItem2);

    cbTreeView.setRoot(rootItem);

Now we create the cell factory, and provide the binding, when the value of the item is set:

    cbTreeView.setCellFactory((TreeView<String> item) -> {

        final CheckBoxTreeCell<String> cell = new CheckBoxTreeCell<>();

        cell.itemProperty().addListener((obs,s,s1)->{
            cell.disableProperty().unbind();
            if(s1!=null && !s1.isEmpty()){
                Wrap wrap=disableList.stream()
                        .filter(w->w.getName().equals(s1))
                        .findFirst().orElse(null);
                if(wrap!=null){
                    cell.disableProperty().bind(wrap.disabledProperty());
                }
            }
        });
        return cell;
    });

And finally, we show the stage, and disable some random items:

    Scene scene=new Scene(cbTreeView);
    primaryStage.setScene(scene);
    primaryStage.show();

    // disable by name
    disableList.stream()
            .filter(w->w.getName().equals("03"))
            .findFirst().ifPresent(w->w.setDisabled(true));
    // disable by order
    disableList.get(7).setDisabled(true);

}

TreeView with disabled items

0
On

Thanks a lot @Pereda. It works good if root is set as independent. Since I am working on Java 7, I replaced the code

Wrap wrap=disableList.stream()
                    .filter(w->w.getName().equals(s1))
                    .findFirst().orElse(null);
            if(wrap!=null){
                cell.disableProperty().bind(wrap.disabledProperty());
            }

with this

for (int i = 0; i < disabledList.size();i++) {
    if (disabledList.get(i).getName().equals(s1)) {
        cell.disabledProperty.bind(disabledList.get(i).disabledProperty());
    }
}

But if root is not set as independent, I click on the root and the disabled child also get a tick which is not correct. So what is the solution in this case ?

0
On

I've came up with the idea of extending the CheckBoxTreeItem class. There I've added the missing disabled property:

import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.CheckBoxTreeItem;


public class CheckBoxTreeItemExt<T> extends CheckBoxTreeItem<T>
{
    public SimpleBooleanProperty disabledProperty = new SimpleBooleanProperty(false);

    public CheckBoxTreeItemExt(T t)
    {
        super(t);
    }

    public boolean isDisabled()
    {
        return disabledProperty.get();
    }

    public void setDisabled(boolean disabled)
    {
        disabledProperty.set(disabled);
    }
}

Then I've created a CheckBoxTreeCell which listens to that new disabledProperty:

import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TreeItem;
import javafx.scene.control.cell.CheckBoxTreeCell;


public class CheckBoxTreeCellExt<T> extends CheckBoxTreeCell<T> implements ChangeListener<Boolean>
{
    protected SimpleBooleanProperty linkedDisabledProperty;

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

        if(item != null)
        {
            TreeItem<T> treeItem = treeItemProperty().getValue();
            if(treeItem != null)
            {
                if(treeItem instanceof CheckBoxTreeItemExt<?>)
                {
                    CheckBoxTreeItemExt<T> checkItem = (CheckBoxTreeItemExt<T>)treeItem;
                    if(checkItem != null)
                    {                               
                        if(linkedDisabledProperty != null)
                        {
                            linkedDisabledProperty.removeListener(this);
                            linkedDisabledProperty = null;
                        }

                        linkedDisabledProperty = checkItem.disabledProperty;                        
                        linkedDisabledProperty.addListener(this);

                        setDisable(linkedDisabledProperty.get());
                    }
                }
            }           
        }
    }

    @Override
    public void changed(ObservableValue<? extends Boolean> arg0, Boolean oldVal, Boolean newVal)
    {       
        setDisable(newVal);
    }
}

And finally we've the CheckBoxTreeCellFactory:

import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeView;
import javafx.util.Callback;

public class CheckBoxTreeCellFactoryExt<T> implements Callback<TreeView<T>, TreeCell<T>>
{
    @Override
    public TreeCell<T> call(TreeView<T> tv)
    {
        return new CheckBoxTreeCellExt<T>();
    }
}
0
On

Extended TreeItem class with bindable Boolean

   import javafx.beans.property.SimpleBooleanProperty;
   import javafx.scene.control.TreeItem;

    public class TreeItemDeactivatable<T> extends TreeItem<T> {
        private SimpleBooleanProperty disabledProperty = new SimpleBooleanProperty(false);

        public TreeItemDeactivatable() {
            super();
        }

        public TreeItemDeactivatable(T t) {
            super(t);
        }

        public SimpleBooleanProperty disabledProperty() {
            return disabledProperty;
        }

        public boolean isDisabled() {
            return disabledProperty.get();
        }

        public void setDisabled(boolean disabled) {
            disabledProperty.set(disabled);
        }
    }

Override call() in CellFactory and put

    @Override
    public TreeCell<T> call(TreeView<T> treeView) {
    TreeCell<T> cell = (TreeCell<T>) new TextFieldTreeCellImpl();

        final PseudoClass clippedStylePseudoClass = PseudoClass.getPseudoClass("clipped");

        cell.treeItemProperty().addListener((ov, oldTreeItem, newTreeItem) -> {
            if (newTreeItem != null && newTreeItem instanceof TreeItemDeactivatable) {
                TreeItemDeactivatable<T> tid = (TreeItemDeactivatable<T>) newTreeItem;
                 tid.setDisabled(false);
                 cell.disableProperty().unbind();
                 cell.disableProperty().bind(tid.disabledProperty());

cell.pseudoClassStateChanged(clippedStylePseudoClass,cell.isDisabled());
            }
        });
}

Use PseudoClass in your CSS setup

.tree-view .tree-cell:clipped {
    -fx-background-color:   color-clipped;
}

Line "cell.pseudoClassStateChanged()" is just in case you need an independent style otherwise you can also use the default pseudo class

.tree-view .tree-cell:disabled {}

how i control the disable state in my controller

@FXML
    private void handleCut() {
        Object selection = getFocusedSelectedItem();

        if (selection != null) {
            this.clipboard.add(selection);

             if (selection instanceof NodeDataModel) {
                NodeDataModel toPaste = (NodeDataModel) selection;
                TreeItemDeactivatable<NodeDataModel> tid = toPaste.getTreeItem();
                tid.setDisabled(true);
            }
        }
    }