功能范例中的 JavaFX ContextMenu

JavaFX ContextMenu in a Functional Paradigm

有没有办法在 JavaFX 中生成上下文菜单并以功能方式获取点击的 MenuItem 或其关联数据?我希望能够做类似的事情:

MenuItem menuItem1 = new MenuItem("Item 1");
menuItem1.setUserData(1);
MenuItem menuItem2 = new MenuItem("Item 2");
menuItem1.setUserData(2);

ContextMenu menu = new ContextMenu(menuItem1, menuItem2);
Integer result = menu.show(...)

if (result == 1)
    ...
else if (result == 2)
    ...
else
    ...

但据我所知,无法模拟 result = menu.show() 行。有办法吗?我希望这是 blocking/synchronous;不像 JavaFX 本身那样基于事件。

你可以。需要注意的是,由于这样的方法会阻塞,因此它必须 运行 在后台线程中(这让事情变得有点难看)。

这是一个例子:

import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public class FunctionalContextMenu extends Application {

    @Override
    public void start(Stage primaryStage) {
        Pane root = new Pane();
        ContextMenu menu = new ContextMenu();
        MenuItem item1 = new MenuItem("Item 1");
        MenuItem item2 = new MenuItem("Item 2");
        menu.getItems().addAll(item1, item2);
        root.setOnContextMenuRequested(e -> {
            showMenu(menu, root, e.getScreenX(), e.getScreenY());
        });

        Scene scene = new Scene(root, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void showMenu(ContextMenu menu, Node anchor, double screenX, double screenY) {
        new Thread(() ->

            showAndWait(menu, anchor, screenX, screenY)
            .ifPresent(item -> System.out.println("You chose "+item.getText()))

        ).start();
    }

    private Optional<MenuItem> showAndWait(ContextMenu menu, Node anchor, double screenX, double screenY) {

        // executing this on the FX Application Thread would cause deadlock,
        // so guard against it:
        if (Platform.isFxApplicationThread()) {
            throw new IllegalStateException("showAndWait cannot be called from the FX Application Thread");
        }

        CountDownLatch latch = new CountDownLatch(1);
        AtomicReference<MenuItem> selectedItem = new AtomicReference<>();
        Platform.runLater(() -> {
            EventHandler<ActionEvent> handler = e -> selectedItem.set((MenuItem)e.getSource());
            menu.setOnHidden(e -> {
                for (MenuItem item : menu.getItems()) {
                    item.removeEventHandler(ActionEvent.ACTION, handler);
                }
                latch.countDown();
            });
            for (MenuItem item : menu.getItems()) {
                item.addEventHandler(ActionEvent.ACTION, handler);
            }
            menu.show(anchor, screenX, screenY);
        });
        try {
            latch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return Optional.ofNullable(selectedItem.get());
    }

    public static void main(String[] args) {
        launch(args);
    }
}

感谢

,这是我结束的地方

enterNestedEventLoopexitNestedEventLoop 方法在 com.sun 中,但在 slated for inclusion 中 public API 用于 Java 9.

import com.sun.javafx.tk.Toolkit;
import javafx.scene.Node;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;

public class FunctionalContextMenu extends ContextMenu {

    /**
     * Create a new FunctionalContextMenu initialized with the given items
     *
     * @param items the initial menu items
     */
    public FunctionalContextMenu(MenuItem... items) {
        super(items);
    }

    /**
     * Shows the context menu and waits until the user clicks an item or
     * otherwise closes the menu. See
     * {@link #show(javafx.scene.Node, double, double)} for more details
     * regarding the display of the menu.
     *
     * @param anchor
     * @param screenX
     * @param screenY
     * @return the clicked menu item, or {@code null} if the menu was closed
     * without selecting a menu item
     */
    public MenuItem showAndWait(Node anchor, double screenX, double screenY) {
        super.show(anchor, screenX, screenY);

        Object lock = new Object();
        this.setOnAction(actionEvt -> Toolkit.getToolkit().exitNestedEventLoop(lock, actionEvt.getTarget()));
        this.setOnAutoHide(autohideEvt -> Toolkit.getToolkit().exitNestedEventLoop(lock, null));
        super.show(anchor, screenX, screenY);
        return (MenuItem) Toolkit.getToolkit().enterNestedEventLoop(lock);
    }
}