JavaFX Menu in JFrame

2019-03-04 10:02发布

问题:

I want to use JavaFX Menu in JFrame. I embedded it using JFXPanel and it is visible in the JFrame. BUT, the problem is, MenuItems don't respond to mouse. I can click the Menu, but not the MenuItems. If i select the MenuItem using keyboard and hit enter, it works. (MenuItems don't get highlighted when i hover mouse over them)

NOTE: I'm not having problem with Events. The click goes on component BELOW the menuItem.

Also, when the swing component is focused and then i want to click on the menu, it needs 2 clicks. First click only focuses the JFXPanel

package notepad;

import java.awt.BorderLayout;
import java.io.IOException;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public class Notepad {

JFrame frame;
JFXPanel panel;
private void initSwing()
{
    frame = new JFrame();
    panel= new JFXPanel();

    frame.setSize(1024,768);
    frame.setLayout(new BorderLayout());
    frame.add(panel, BorderLayout.NORTH);
    Platform.runLater(() -> initFX(panel));

    frame.add(new JTextArea(), BorderLayout.CENTER);
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

}
private void initFX(JFXPanel jfxPanel) {
    try {
        Parent root = FXMLLoader.load(getClass().getResource("FXBars.fxml"));
        Scene scene = new Scene(root);


        jfxPanel.setScene(scene);
    } catch (IOException exc) {
        exc.printStackTrace();
        System.exit(1);
    }
}
public static void main(String[] args) {
    Notepad test = new Notepad();
    SwingUtilities.invokeLater(() -> test.initSwing() );
    }

}

FXML file:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Button?>

<MenuBar xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="notepad.FXBarsController">
   <menus>
      <Menu mnemonicParsing="false" text="File">
         <items>
            <MenuItem mnemonicParsing="false" onAction="#menuClose" text="Close" />
         </items>
      </Menu>
      <Menu mnemonicParsing="false" text="Edit">
         <items>
            <MenuItem mnemonicParsing="false" text="Delete" />
         </items>
      </Menu>
      <Menu mnemonicParsing="false" text="Help">
         <items>
            <MenuItem mnemonicParsing="false" text="About" />
         </items>
      </Menu>
   </menus>
</MenuBar>

FXML Controller:

package notepad;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;


public class FXBarsController implements Initializable {

    @FXML
    private void menuClose()
    {
        System.out.println("CLOSE");
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {

    }

}

回答1:

This is how it basically works.

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TestSwingIntegration {

    private static void initAndShowGUI() {

        JFrame frame = new JFrame("FX");
        final JFXPanel fxPanel = new JFXPanel();
        frame.add(fxPanel);
        frame.setVisible(true);
        frame.setSize(800, 600);

        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                initFX(fxPanel);
            }
        });
    }

    private static void initFX(JFXPanel fxPanel) {

        Group root = new Group();

        MenuBar menuBar = new MenuBar();
        Menu menuFile = new Menu("File");
        MenuItem miClose = new MenuItem("Close");
        miClose.setOnAction(new EventHandler<ActionEvent>() {
            public void handle(ActionEvent t) {
                System.out.println( "Close pressed");
            }
        });      
        menuFile.getItems().add( miClose);

        menuBar.getMenus().addAll(menuFile);

        root.getChildren().add( menuBar);

        Scene scene = new Scene(root, 200, 200);
        fxPanel.setScene(scene);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                initAndShowGUI();
            }
        });
    }

}

Click File -> Close, check the console output.



回答2:

The requestFocus() method call, in the processMouseEvent() method of the JFXPanel class, is sent to the EventQueue and handled asynchronously and thus the click is not considered (due to lack of focus in the FX component).

To cascade the click to the underlying menu item, the same mouse click should be fired again when the FXPanel comes into focus.

To do it,

  • Extend the JFXPanel Class and create your own CustomeJFXPanel (CustomJFXPanel extends JFXPanel).
  • Override the processMouseEvent(MouseEvent e) method as below.

    @Override        
    protected void processMouseEvent(MouseEvent e) {        
        try {       
            if ((e.getID() == MouseEvent.MOUSE_PRESSED)&& (e.getButton() == MouseEvent.BUTTON1)) {       
                if (!hasFocus()) {       
                    requestFocus();        
                    AppContext context = SunToolkit.targetToAppContext(this);        
                    if (context != null) {        
                        SunToolkit.postEvent(context, e);        
                    }        
                }       
            }        
        } catch (Exception ex) {        
            //DO SOMETHING        
        }        
        super.processMouseEvent(e);        
    }
    
  • Replace JFXPanel() class by your new CustomJFXPanel() class.

This is fixed in JDK9.

(Collected from another source)