Is this possible that i can add context menu at TreeItem rather then TreeView in JavaFX


Question

i am new to JavaFX. I made the custom TreeCell using this reference:-

http://fxexperience.com/2012/05/listview-custom-cell-factories-and-context-menus/

It perfectly worked for me. But my case is that i do not want to apply this context menu to child nodes. At start there is only single TreeItems and at later stage these TreeItems have child so the try to check isLeaf() method does not make a sense because at starting the TreeItems are at leaf level but later they become parent.

So any idea how can i achieve this.

Thanks a lot.

Update:
At initial stage of my project there is tree having three childs(mail service providers) as follows:-

---Gmail  
---Yahoo  
---Rediff  

After i connect these providers then they have this structure:-

Gmail
   |____Inbox
   |____Sent
Yahoo
   |___Inbox
   |___Sent
   |___Drafts
Rediff
   |___Inbox

Now i want context menu only on Gmail,Yahoo and rediff and not on the inbox or sent item.

I know the setContextmenu() method but this is available for the TreeView and not for the TreeItem.

1
2
12/19/2013 8:26:09 PM

Accepted Answer

I would make your tree items a descendant class of TreeItem like public class ProviderTreeItem extends TreeItem and a different descendant with the boxes like BoxTreeItem.

That way you won't test for isLeaf but you can test the TreeItem type.

if (thisTreeItem.getClass() == ProviderTreeItem.class)
   thisTreeItem.setContextmenu(providerContextMenu)
else thisTreeItem.setContextmenu(boxContextMenu)

It would be easier to just get the context menu from the subclassed TreeItem.

You don't really need to subclass TreeItem, you could just use the name or something like the userData object to distinguish between node types. If you subclass then you can add different methods and properties to the different node types.

For example the boxes will need a data structure to hold emails but the the providers don't need this. The providers need a web address and password etc., but the boxes don't need that.

Here's the updated example.

import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class TVexample extends Application {
public abstract class AbstractTreeItem extends TreeItem{
    public abstract ContextMenu getMenu();
}

public class ProviderTreeItem extends AbstractTreeItem{
    // make class vars here like psswd
    public ProviderTreeItem(String name) {
        this.setValue(name);
    }

    @Override
    public ContextMenu getMenu(){
        MenuItem addInbox = new MenuItem("add inbox");
        addInbox.setOnAction(new EventHandler() {
            public void handle(Event t) {
                BoxTreeItem newBox = new BoxTreeItem("inbox");
                    getChildren().add(newBox);
            }
        });
        return new ContextMenu(addInbox);
    }
}

public class BoxTreeItem extends AbstractTreeItem{
    //private List<String> emails = new LinkedList<>();
    public BoxTreeItem(String name) {
        this.setValue(name);
    }

    @Override
    public ContextMenu getMenu() {
        return new ContextMenu(new MenuItem("test"));
    }
}

private final class TreeCellImpl extends TreeCell<String> {

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

        if (empty) {
            setText(null);
            setGraphic(null);
        } else {
            setText(getItem() == null ? "" : getItem().toString());
            setGraphic(getTreeItem().getGraphic());
            setContextMenu(((AbstractTreeItem) getTreeItem()).getMenu());
        }
    }
}

@Override
public void start(Stage primaryStage) {
    StackPane sceneRoot = new StackPane();
    TreeItem treeRoot = new TreeItem();
    treeRoot.setExpanded(true);
    ProviderTreeItem gm = new ProviderTreeItem("gmail");
    ProviderTreeItem yh = new ProviderTreeItem("yahoo");
    treeRoot.getChildren().addAll(gm,yh);
    TreeView<String> treeView = new TreeView<String>(treeRoot);
    treeView.setShowRoot(false);
    treeView.setCellFactory(new Callback<TreeView<String>,TreeCell<String>>(){
        @Override
        public TreeCell<String> call(TreeView<String> p) {
            return new TreeCellImpl();
        }
    });
    sceneRoot.getChildren().add(treeView);
    Scene scene = new Scene(sceneRoot, 300, 500);


    primaryStage.setScene(scene);
    primaryStage.show();
}

}

5
12/20/2013 3:34:47 PM

Here is my favorite utility method for defining cellFactory in TreeView. Firstly, it sets TreeCell conversion to String, secondly, it applies ContextMenu.

public static <T> void setCellFactory(TreeView<T> treeView,
                                      ToStringConverter<T> converter,
                                      ContextMenu contextMenu) {

treeView.setCellFactory(tc -> {

  TreeCell<T> cell = new TreeCell<T>() {

    @Override
    protected void updateItem(T item, boolean empty) {
      super.updateItem(item, empty);
      if (empty) {
        setText(null);
      } else {
        setText(converter.toString(item));
      }
    }

  };

  cell.emptyProperty()
      .addListener((obs, wasEmpty, isNowEmpty) -> {
        if (isNowEmpty) {
          cell.setContextMenu(null);
        } else {
          cell.setContextMenu(contextMenu);
        }
      });

  return cell;

  });

}

ToStringCoverter is interface

public interface ToStringConverter<T> {
  String toString(T t);
}

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon