2016-04-28 6 views
0

Есть ли способ создать контекстное меню в JavaFX и получить щелчок MenuItem или связанные с ним данные функциональным способом? Я хотел бы быть в состоянии сделать что-то вроде:JavaFX ContextMenu в функциональной парадигме

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() линию. Есть ли способ? Я хочу, чтобы это было блокирование/синхронно; не основанный на событиях, как JavaFX.

ответ

0

Вот где я в конечном итоге, благодаря https://stackoverflow.com/a/27081164/2270967

Методы enterNestedEventLoop и exitNestedEventLoop находятся в com.sun, но являются slated for inclusion в открытый 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); 
    } 
} 
2

Вы можете сортировать. Предостережение заключается в том, что, поскольку такой метод блокируется, он должен выполняться в фоновом потоке (что делает вещи немного уродливыми).

Вот пример:

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); 
    } 
} 
+0

Спасибо! Просматривая документы JavaFX немного, я думаю, что я мог бы использовать [Task] (http://docs.oracle.com/javafx/2/api/javafx/concurrent/Task.html), чтобы полностью обернуть поток showAndWait(). –

Смежные вопросы