【问题标题】:ContextMenu in JavaFX keeps getting duplicates of the same MenuItemJavaFX 中的 ContextMenu 不断获得相同 MenuItem 的重复项
【发布时间】:2015-04-01 04:18:56
【问题描述】:

我使用这个类为 TreeItem 提供一个用于编辑的文本字段(与问题无关)并在 TreeItem 上设置一个 ContextMenu:

package domain;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

public final class TextFieldTreeCellImpl extends TreeCell<MyNode> {

    private TextField textField;
    private ContextMenu cm = new ContextMenu();

    String oldItem = "";

    private Connection connection;
    String url = "jdbc:sqlserver://localhost:1433;databaseName=HOGENT1415_11";
    String user = "sa";
    String password = "root";
    Statement statement;

    public TextFieldTreeCellImpl() throws SQLException {
        connection = DriverManager.getConnection(url, user, password);
        statement = connection.createStatement();
    }

    @Override
    public void startEdit() {
        super.startEdit();

        if (textField == null) {
            createTextField();
        }
        setText(null);
        setGraphic(textField);
        textField.selectAll();
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();
        setText((String) getItem().value);
        setGraphic(getTreeItem().getGraphic());
    }

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

        super.updateItem(item, empty);

        if (empty) {
            setText(null);
            setGraphic(null);
        } else {
            if (isEditing()) {
                if (textField != null) {
                    textField.setText(getString());
                }
                setText(null);
                setGraphic(textField);
            } else {
                setText(getString());
                setGraphic(getTreeItem().getGraphic());
                
                MenuItem cmItem1 = new MenuItem("Add continent");
                cmItem1.setOnAction(new EventHandler<ActionEvent>() {
                    @Override
                    public void handle(ActionEvent e) {
                        System.out.println("Geklikt!");
                    }
                });
                cm.getItems().add(cmItem1);
                setContextMenu(cm);
            }
        }

    }

    private void createTextField() {
        textField = new TextField(getString());
        textField.setOnKeyReleased(new EventHandler<KeyEvent>() {

            @Override
            public void handle(KeyEvent t) {
                if (t.getCode() == KeyCode.ENTER) {

                    String sql = "UPDATE Continents SET Name='" + textField.getText() + "' WHERE ContinentID=" + getItemId();
                    if (getItem().isCountry()) {
                        sql = "UPDATE Countries SET Name='" + textField.getText() + "' WHERE CountryID=" + getItemId();
                    }
                    if (getItem().isClimateChart()) {
                        sql = "UPDATE ClimateCharts SET Location='" + textField.getText() + "' WHERE ClimateChartID=" + getItemId();
                    }

                    try {
                        ResultSet result = statement.executeQuery(sql);
                    } catch (SQLException ex) {
                        Logger.getLogger(TextFieldTreeCellImpl.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    commitEdit(new MyNode(textField.getText(), getType(), getItemId()));
                } else if (t.getCode() == KeyCode.ESCAPE) {
                    cancelEdit();
                }
            }
        });
    }

    private String getString() {
        return getItem() == null ? "" : getItem().toString();
    }

    private String getType() {
        return getItem() == null ? "" : getItem().type;
    }

    private int getItemId() {
        return getItem() == null ? null : getItem().id;
    }
}

我使用以下代码在我的 Controller 类中实例化这个类:

selectionTreeView.setEditable(true);
        selectionTreeView.setCellFactory(new Callback<TreeView<MyNode>, TreeCell<MyNode>>() {
            @Override
            public TreeCell<MyNode> call(TreeView<MyNode> p) {
                try {
                    return new TextFieldTreeCellImpl();
                } catch (SQLException ex) {
                    Logger.getLogger(MainPanel.class.getName()).log(Level.SEVERE, null, ex);
                }
                return null;
            }

        });

但是,当我运行程序并右键单击项目时,一切正常,但如果我继续在其他项目上单击更多,则会继续在上下文菜单中获取项目。

为了缩小范围,每次双击某个项目时都会发生这种情况。

查看截图:

我知道这是因为 ipdateItem 不断被调用,但我该如何防止呢?

【问题讨论】:

    标签: javafx treeview contextmenu


    【解决方案1】:

    每次调用updateItem(...) 时,都会再次添加菜单项(并且永远不会删除它)。因此,每次重用单元格时,它都会获得菜单项的另一个副本。

    最有效的方法是在构造函数中创建菜单项并将其传递给那里的上下文菜单。请注意,事件处理程序可以轻松访问当前项目:

    public TextFieldTreeCellImpl() throws SQLException {
        connection = DriverManager.getConnection(url, user, password);
        statement = connection.createStatement();
    
        MenuItem cmItem1 = new MenuItem("Add continent");
        cmItem1.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent e) {
                MyNode item = getItem();
                // ...
                System.out.println("Geklikt!");
            }
        });
        cm.getItems().add(cmItem1);
    
    }
    

    您可能还想删除空单元格的上下文菜单:

    @Override
    public void updateItem(MyNode item, boolean empty) {
    
        super.updateItem(item, empty);
        if (empty) {
            setText(null);
            setGraphic(null);
            setContextMenu(null);
        } else {
            if (isEditing()) {
                if (textField != null) {
                    textField.setText(getString());
                }
                setText(null);
                setGraphic(textField);
            } else {
                setText(getString());
                setGraphic(getTreeItem().getGraphic());
    
                setContextMenu(cm);
            }
        }
    }
    

    如果需要,您还可以在updateItem(...) 方法中进一步配置菜单项,将它们声明为字段,例如

    public final class TextFieldTreeCellImpl extends TreeCell<MyNode> {
    
        private TextField textField;
        private ContextMenu cm = new ContextMenu();
    
        private MenuItem cmItem1 ;
    
        // ...
    
        @Override
        public void updateItem(MyNode item, boolean empty) {
            super.updateItem(item, empty);
    
            // ...
    
            cmItem1.setText(...);
    
        }
    }
    

    最后,如果你真的需要在项目改变时完全重构上下文菜单,那么你可以这样做

        @Override
        public void updateItem(MyNode item, boolean empty) {
            super.updateItem(item, empty);
    
            // ...
    
            cm.getItems().clear();
            // Now create all menu items from scratch and add to the context menu
    
        }
    

    【讨论】:

    • 这行得通,有没有办法在构造函数中获取 MyNode 项?我需要这个,因为我需要根据 TreeItem 的级别提供不同的上下文菜单
    • 在事件处理程序中,是的。本质上,您无法在构造函数中获取它,因为那时还没有设置它...查看更新。
    • 我明白了,现在一切正常,感谢您的启发!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-13
    • 1970-01-01
    • 2010-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多