JavaFX中的ContextMenu不断获取同一MenuItem的副本

时间:2015-03-31 19:38:39

标签: javafx treeview contextmenu

我使用此类为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;
            }

        });

然而,当我运行程序并且我右键单击项目时,一切正常,但如果我继续点击其他项目,那么继续在上下文菜单中获取项目。

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

查看屏幕截图:

Everything working fine

After some doubleclicks

我知道这是因为ipdateItem一直被调用,但我怎么能阻止它?

1 个答案:

答案 0 :(得分:2)

每次调用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

    }