2016-04-28 5 views
0

Gibt es eine Möglichkeit, ein Kontextmenü in JavaFX zu generieren und die angeklickte MenuItem oder die damit verbundenen Daten auf eine funktionale Weise zu erhalten? Ich möchte in der Lage sein, etwas zu tun:JavaFX ContextMenu in einem funktionalen Paradigma

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 
    ... 

Aber meines Wissens gibt es keine Möglichkeit, die result = menu.show() Linie zu emulieren. Gibt es einen Weg? Ich möchte, dass dies blockiert/synchron ist; nicht ereignisbasiert, wie es JavaFX nativ tut.

Antwort

0

Hier ist, wo ich am Ende, dank https://stackoverflow.com/a/27081164/2270967

Die enterNestedEventLoop und exitNestedEventLoop Methoden in com.sun sind aber slated for inclusion in das öffentliche API für Java 9.

sind
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

Sie können sortieren. Der Nachteil ist, dass, da eine solche Methode blockiert, sie in einem Hintergrund-Thread ausgeführt werden muss (was die Dinge ein bisschen hässlich macht).

Hier ist ein Beispiel:

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

Danke! Wenn ich die JavaFX-Dokumentationen ein wenig durchführe, denke ich, dass ich möglicherweise einen [Task] (http://docs.oracle.com/javafx/2/api/javafx/concurrent/Task.html) verwenden kann, um den Thread vollständig darin zu verpacken showAndWait(). –