JavaFX如何在使用自定义单元工厂时正确处理事件?

时间:2017-01-10 07:18:40

标签: java javafx java-8 javafx-2 javafx-8

如果这个问题在您的观点中没有多大意义,我真的很抱歉,但从我的角度来看,编写更清晰,更易维护的代码非常重要。

我有A.fxml,AController.java(A.fxml的控制器类)。我有一个定义了自定义单元工厂的TableView。我更喜欢在单独的类中定义我的单元工厂,以便在需要时可以重用它们。我更喜欢在我的控制器类中编写所有事件处理代码。但是,如果我使用自定义单元工厂,那么我不得不在单元工厂类本身中编写事件处理。

有没有办法可以在我的控制器类本身处理自定义单元工厂事件?或者至少只是将事件从自定义单元工厂类抛出到我的控制器类并处理?

先谢谢。

2 个答案:

答案 0 :(得分:4)

您可以将对象传递给工厂,该工厂确定何时应打开上下文菜单并准备菜单。

示例:

public interface CellContextMenuProvider<S, T> {

    /**
     * Prepares the context menu for opening.
     * @param cell the cell the menu was requested for
     * @param menu the menu to prepare
     */
    public void prepareContextMenu(TableCell<S, T> cell, ContextMenu menu);

    /**
     * Checks, if a cell continaing a certain item should have an active context
     * menu.
     * @param empty if the cell is empty
     * @param item the item of the cell
     * @return {@literal true} iff the context menu should be enabled.
     */
    public boolean enableContextMenu(boolean empty, T item);

    /**
     * Prepares the intial menu. This menu must not be empty, otherwise it won't
     * be shown when it's requested for the first time.
     * @param menu the menu to prepare
     */
    public void prepareInitialContextMenu(ContextMenu menu);

}
public class CellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {

    private final CellContextMenuProvider<S, T> menuProvider;
    private final ContextMenu contextMenu;

    public CellFactory(@NamedArg("menuProvider") CellContextMenuProvider<S, T> menuProvider) {
        this.menuProvider = menuProvider;

        if (menuProvider == null) {
            this.contextMenu = null;
        } else {
            this.contextMenu = new ContextMenu();
            menuProvider.prepareInitialContextMenu(contextMenu);
        }

        this.menuEventHandler = evt -> {
            if (this.contextMenu != null) {
                TableCell<S, T> source = (TableCell<S, T>) evt.getSource();
                this.menuProvider.prepareContextMenu(source, this.contextMenu);
            }
        };
    }

    public CellFactory() {
        this(null);
    }

    private final EventHandler<ContextMenuEvent> menuEventHandler;

    @Override
    public TableCell<S, T> call(TableColumn<S, T> param) {
        TableCell<S, T> result = new TableCell<S, T>() {

            @Override
            protected void updateItem(T item, boolean empty) {
                super.updateItem(item, empty);
                setText(Objects.toString(item, ""));
                setContextMenu(menuProvider != null && menuProvider.enableContextMenu(empty, item) ? contextMenu : null);
            }

        };
        result.setOnContextMenuRequested(menuEventHandler);
        if (menuProvider != null && menuProvider.enableContextMenu(true, null)) {
            result.setContextMenu(contextMenu);
        }
        return result;
    }

}
public class AController {

    @FXML
    private TableView<Item<Integer>> table;

    public void initialize() {
        for (int i = 0; i < 100; i++) {
            table.getItems().add(new Item<>(i));
        }
    }    

    public CellContextMenuProvider<Item<Integer>, Integer> getMenuProvider() {
        return new CellContextMenuProvider<Item<Integer>, Integer>() {

            private final MenuItem item = new MenuItem("Say Hello World");

            {
                item.setOnAction(evt -> System.out.println("Hello World"));
            }

            @Override
            public void prepareContextMenu(TableCell<Item<Integer>, Integer> cell, ContextMenu menu) {
            }

            @Override
            public void prepareInitialContextMenu(ContextMenu menu) {
                menu.getItems().setAll(item);
            }

            @Override
            public boolean enableContextMenu(boolean empty, Integer item) {
                // only for odd items
                return !empty && (item % 2) != 0;
            }

        };
    }

}

<强> A.fxml

<TableView fx:id="table" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxml.table.AController">
  <columns>
      <TableColumn prefWidth="159.0" text="C1">
          <cellValueFactory>
              <PropertyValueFactory property="value" />
          </cellValueFactory>
          <cellFactory>
              <CellFactory menuProvider="$controller.menuProvider"/>
          </cellFactory>
      </TableColumn>
  </columns>
</TableView>

注意:如果上下文菜单始终相同,您还可以将EventHandler属性添加到工厂并使用它们,例如按钮的onAction属性,允许您传递控制器的事件处理程序,这将导致更短/更简单的代码。

答案 1 :(得分:3)

是否有可能在您的单元工厂中为您的事件处理提供FunctionalInterface参数?(不确定是否有好主意)

我想你的代码如下:

控制器:

myTableView.setCellFactory(new MyOwnCellFactory<>(() -> { 
    // event handling 
}));

MyOwnCellFactory:

public MyOwnCellFactory(MyFunctionalInterface myInterface) {
    functionalInterface = myInterface;
}

// something something

functionalInterface.handleEvent();

FunctionalInterface:

@FunctionalInterface
public interface MyFunctionalInterface {
    public void handleEvent();
}

不确定我的理念是否正确。没有测试代码,只是把它写出来。