简体   繁体   中英

How do i put a focus listener on a Treecell in JavaFX?

I am building am application where I need to have a tree view alongside a pane where data is presented. When someone selects an item in the tree view, the application must identify what they have selected and seek the correct data from a database, then present it in the format I've chosen. It must not matter how the user selects the item in the tree view (mouse click, tabbing, arrow keys, etc), just that when an item gets focus, a method is triggered to present the data to the user.

I got this working perfectly for mouse clicks only in the following way:

    // Application thread method to build the tree map, used in the generateTree
    // method.
    public void treeBuilder(TreeMap<ModelSites, ArrayList<ModelPlants>> map) {
        
        TreeMap<ModelSites, ArrayList<ModelPlants>> treeMap = map;

        final TreeItemProperties<String, String> rootTreeItem = new TreeItemProperties<String, String>("EMT", null);

        TreeItemProperties<String, Integer> site = null;
        TreeItemProperties<String, Integer> plant = null;
        
        for (Map.Entry<ModelSites, ArrayList<ModelPlants>> entry : treeMap.entrySet()) {
            site = new TreeItemProperties<String, Integer>(entry.getKey().getLongName(), entry.getKey().getPrimaryKey());
            rootTreeItem.getChildren().add(site);

            if (site.getValue().equalsIgnoreCase("test item")) {
                site.setExpanded(true);
            }

            for (int i = 0; i < entry.getValue().size(); i++) {
                plant = new TreeItemProperties<String, Integer>(entry.getValue().get(i).getSitePlantId() + " " + entry.getValue().get(i).getShortName(), entry.getValue().get(i).getPrimaryKey());
                site.getChildren().add(plant);
            }
        }
        
        //Cell Factory is used to effectively turn the tree items into nodes, which they are not natively.
        //This is necessary to have actions linked to the tree items (eg. double click an item to open an edit window).
        emtTree.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {

            @Override
            public TreeCell<String> call(TreeView<String> param) {
                FactoryTreeCell<String> cell = new FactoryTreeCell<String>();

                cell.setOnMouseClicked(event -> {
                    if (!cell.isEmpty()) {
                        @SuppressWarnings("unchecked")
                        TreeItemProperties<String, Integer> treeItem = (TreeItemProperties<String, Integer>) cell.getTreeItem();
                        generateEquipmentPanes(treeItem.getPropertyValue());
                    }
                });
                return cell;
            }
        });

        rootTreeItem.setExpanded(true);
        emtTree.setRoot(rootTreeItem);
    }

// Populate the main screen with all equipment items in the selected plant.
    public void generateEquipmentPanes(int plantId) {

        int plant = plantId;
        
        Task<LinkedList<ModelEquipment>> task = new Task<LinkedList<ModelEquipment>>() {
            @Override
            public LinkedList<ModelEquipment> call() {
                LinkedList<ModelEquipment> equipmentList = DAOEquipment.listEquipmentByPlant(plant);
                return equipmentList;
            }
        };

        // When list is built successfully, send the results back to the application
        // thread to load the equipment panes in the GUI.
        task.setOnSucceeded(e -> equipmentPaneBuilder(task.getValue()));
        task.setOnFailed(e -> task.getException().printStackTrace());
        task.setOnCancelled(null);

        String methodName = new Object() {}.getClass().getEnclosingMethod().getName();

        Thread thread = new Thread(task);
        thread.setName(methodName);
        //System.out.println("Thread ID: " + thread.getId() + ", Thread Name: " + thread.getName());
        thread.setDaemon(true);
        thread.start();
    }

    // Application thread method to build the equipment panes, used in the
    // generateEquipmentPanes method.
    public void equipmentPaneBuilder(LinkedList<ModelEquipment> list) {
        LinkedList<ModelEquipment> equipmentList = list;
        
        EquipmentPanels.getChildren().clear();

        for (int i = 0; i < equipmentList.size(); i++) {
            ModelEquipment item = equipmentList.get(i);

            try {
                PaneEquipment equipmentPane = new PaneEquipment();
                equipmentPane.updateFields(item.getTechId(), item.getShortName(), item.getLongDesc()); equipmentPane.setId("equipPane" + i);
                            
                EquipmentPanels.getChildren().add(equipmentPane);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

I've done a tonne of searching and I sort of figured out how to implement listeners instead of handlers as this seemed to be the way to do what I want - put a listener on a property of the cell. But when replace the event handler with the listener, like the below two examples, I get many issues.

                emtTree.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
                    if (newValue != null) {
                        @SuppressWarnings("unchecked")
                        TreeItemProperties<String, Integer> treeItem = (TreeItemProperties<String, Integer>) cell.getTreeItem();
                        generateEquipmentPanes(treeItem.getPropertyValue());
                    }
                });
                cell.focusedProperty().addListener(new ChangeListener<Boolean>() {

                    @Override
                    public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                        if (!cell.isEmpty()) {
                            @SuppressWarnings("unchecked")
                            TreeItemProperties<String, Integer> treeItem = (TreeItemProperties<String, Integer>) cell.getTreeItem();
                            generateEquipmentPanes(treeItem.getPropertyValue());
                        }
                    }
                    
                });

For a start I get nullpointerexceptions every time I click on a tree item, which comes from the line generateEquipmentPanes(treeItem.getPropertyValue()); . Secondly, it tends to pick data from the wrong item, not the one I selected. Then after a few clicks it seems to break down altogether and do nothing except provide more nullpointerexceptions .

From what I can understand, I think that the issue is the location of the listener relative to the variables that need to be passed to the method generateEquipmentPanes . And something about removing listeners at a certain point and re-adding them later.

Should I somehow be putting the listener into the cell factory? At the moment it just looks like this:

import javafx.scene.control.TreeCell;

public class FactoryTreeCell<T> extends TreeCell<T> {
    
    public FactoryTreeCell() {
    }
    
    /*  
     * The update item method simply displays the cells in place of the tree items (which disappear when setCellFactory is set.
     * This can be used for many more things (such as customising appearance) not implemented here.
    */
    @Override
    protected void updateItem(T item, boolean empty) {
        super.updateItem(item, empty);
        
        if (empty || item == null) {
            setText(null);
            setGraphic(null);   //Note that a graphic can be many things, not just an image.  Refer openJFX website for more details.
        } else {
            setText(item.toString());
        }
    }
    
}

Another thing I've noticed is that there are other ways to implement the Callback , that might work better with listeners, but I don't know how to do that.

I've been stuck on this for ages so a breakthrough would be great.

You should not be using a tree cell to examine the selected value. Your ChangeListener already receives the new value directly:

emtTree.getSelectionModel().selectedItemProperty().addListener(
    (observable, oldSelection, newSelection) -> {
        if (newSelection != null) {
            TreeItemProperties<String, Integer> treeItem = newSelection;
            generateEquipmentPanes(treeItem.getPropertyValue());
        }
    });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM