【问题标题】:JavaFX How to handle events properly when using custom cell factories?JavaFX 使用自定义单元工厂时如何正确处理事件?
【发布时间】:2017-01-10 07:18:40
【问题描述】:

如果这个问题在你看来没有多大意义,我真的很抱歉,但在我看来,这对于编写更清晰、更可维护的代码非常重要。

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

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

提前致谢。

【问题讨论】:

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


    【解决方案1】:

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

    例子:

    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 按钮的属性,这将允许您传递控制器的事件处理程序,这将导致更短/更简单的代码。

    【讨论】:

    • 感谢您抽出宝贵时间。你的答案是有效的。但我不确定我是否完全理解。让我通过代码并尝试理解。谢谢
    【解决方案2】:

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

    我想你的代码如下:

    控制器:

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

    MyOwnCellFactory:

    public MyOwnCellFactory(MyFunctionalInterface myInterface) {
        functionalInterface = myInterface;
    }
    
    // something something
    
    functionalInterface.handleEvent();
    

    功能接口:

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

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

    【讨论】:

    • 感谢您抽出宝贵时间。我尝试了你的解决方案,它正在工作。几无疑问。 1. 正如你所说,这是个好主意吗?有什么副作用吗? 2. 如果我从 fxml 设置 cellFactory 怎么办? 3. 如果我的单元工厂类和单元类分别实现,如何做同样的事情。我的意思是我有一个返回 TableCell 的 ButtonCell 类,并且我有一个 ButtonCellFactory 类,它只调用 ButtonCell 就像“return new ButtonCell();”来自 ButtonCellFactory 类的 call() 方法。谢谢!
    • 1) 不确定副作用。好吧,至少您的功能界面中不能有多个事件处理程序。因此,如果您想拥有不同的事件处理程序,则需要更改它。 2)猜猜这种方法不会很好用 3)我猜你仍然可以将你的 FunctionalInterface 对象转发到单元格并做任何你想做的事情(调用事件处理程序或其他)最后它可能只是一个直截了当和易于理解的方法。但另一个答案可能更好:)
    猜你喜欢
    • 2013-10-15
    • 2016-04-22
    • 2014-07-23
    • 1970-01-01
    • 2021-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-24
    相关资源
    最近更新 更多