官术网_书友最值得收藏!

Adding menus

Menus in JavaFX start with a component called MenuBar. We want this menu to be at the top of the window, of course, so we add the component to the top section of our BorderPane. If you use Scene Builder, you will end up with something like this in your FXML file:

    <MenuBar BorderPane.alignment="CENTER"> 
      <menus> 
        <Menu mnemonicParsing="false" text="File"> 
          <items> 
            <MenuItem mnemonicParsing="false" 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> 

We won't be needing the edit menu, so we can remove that section from the FXML file (or by right-clicking on the second Menu entry in Scene Builder and clicking on Delete). To create the menu items we do want, we add the appropriate MenuItem entries to the item element under the File element:

    <Menu mnemonicParsing="true" text="_File"> 
      <items> 
        <MenuItem mnemonicParsing="true"  
          onAction="#runProcessHandler"  
          text="_New Process..." /> 
        <MenuItem mnemonicParsing="true"  
          onAction="#killProcessHandler"  
          text="_Kill Process..." /> 
        <MenuItem mnemonicParsing="true"  
          onAction="#closeApplication"  
          text="_Close" /> 
      </items> 
    </Menu> 

Each of these MenuItem entries has three attributes defined:

  • mnemonicParsing: This instructs JavaFX to use any letter prefixed with an underscore as a keyboard shortcut
  • onAction: This identifies the method on the controller that will be called when MenuItem is activated/clicked
  • text: This defines the label of MenuItem

The most interesting part is onAction and its relationship with the controller. JavaFX, of course, already knows that this form is backed by com.steeplesoft.procman. Controller, so it will look for a method with the following signature:

    @FXML 
    public void methodName(ActionEvent event) 

ActionEvent is a class that is used in a number of scenarios by JavaFX. In our case, we have methods specifically for each menu item, so the event itself isn't too terribly interesting. Let's take a look at each handler, starting with the simplest--closeApplication:

    @FXML 
    public void closeApplication(ActionEvent event) { 
      Platform.exit(); 
    } 

There's nothing much to see here; when the menu item is clicked, we exit the application by calling Platform.exit().

Next up, let's see how to kill a process:

    @FXML 
    public void killProcessHandler(final ActionEvent event) { 
      new Alert(Alert.AlertType.CONFIRMATION,  
      "Are you sure you want to kill this process?",  
      ButtonType.YES, ButtonType.NO) 
       .showAndWait() 
       .filter(button -> button == ButtonType.YES) 
       .ifPresent(response -> { 
         ProcessHandle selectedItem =  
          processView.getSelectionModel() 
.getSelectedItem(); if (selectedItem != null) { selectedItem.destroy(); processListUpdater.updateList(); } }); }

We have quite a bit going on here. The first thing we do is to create an Alert box of type CONFIRMATION, which asks the user to confirm the request. The dialog has two buttons: YES and NO. Once the dialog has been created, we call showAndWait(), which does as its name implies--it shows the dialog and waits for the user's response. It returns Optional<ButtonType>, which holds the type of the button that the user clicked on, which will either be ButtonType.YES or ButtonType.NO, given the type of Alert box we've created. With Optional, we can apply filter() to find only the type of button that we're interested in, which is ButtonType.YES, the result of which is another Optional. If the user clicked on yes, ifPresent() will return true (thanks to our filter), and the lambda we passed in will be executed. Very nice and concise.

The next area of interest is that lambda. Once we've identified that the user would like to kill a process, we need to identify which process to kill. To do that, we ask TableView which row is selected via TableView.getSelectionModel() .getSelectedItem(). We do need to check for null (alas, there's no Optional here) in the event that the user has not actually selected a row. If it is non-null, we can call destroy() on the ProcessHandle the TableView gives us. We then call processListUpdater.updateList() to refresh the UI. We'll take a look at that later.

Our final action handler has to run the following command:

    @FXML 
    public void runProcessHandler(final ActionEvent event) { 
      final TextInputDialog inputDlg = new TextInputDialog(); 
      inputDlg.setTitle("Run command..."); 
      inputDlg.setContentText("Command Line:"); 
      inputDlg.setHeaderText(null); 
      inputDlg.showAndWait().ifPresent(c -> { 
        try { 
          new ProcessBuilder(c).start(); 
        } catch (IOException e) { 
            new Alert(Alert.AlertType.ERROR,  
              "There was an error running your command.") 
              .show(); 
          } 
      }); 
    } 

This is, in many ways, similar to the preceding killProcessHandler() method--we create a dialog, set some options, call showAndWait(), then process Optional. Unfortunately, the dialog doesn't support the builder pattern, meaning we don't have a nice, fluid API to build the dialog, so we do it in several discrete steps. Processing Optional is also similar. We call ifPresent() to see if the dialog returned a command line (that is, the user entered some text and pressed OK), and pass that to the lambda if present.

Let's take a quick look at the lambda. This is another example of a multiline lambda. Whereas most lambdas we've seen so far have been simple, one-line functions, remember that a lambda can span multiple lines. All that needs to be done to support that is to wrap the block in curly braces as we've done, and it's business as usual. Care must be taken with multiline lambdas like this, as any gains in readability and conciseness that lambdas give us can be quickly obscured or erased by a lambda body that grows too large. In those instances, extracting the code out to a method and using a method reference might be the wise thing to do. Ultimately, the decision is yours, but remember the words of Uncle Bob Martin--Clarity is king.

One final item on the topic of menus. To be even more useful, the application should provide a context menu that will allow the user to right-click on a process and kill it from there, as opposed to clicking on the row, moving the mouse to the File menu, and more. Adding a context menu is a simple operation. All we need to do is modify our TableView definition in FXML like this:

    <TableView fx:id="processView" BorderPane.alignment="CENTER"> 
      <contextMenu> 
        <ContextMenu> 
          <items> 
            <MenuItem onAction="#killProcessHandler"  
               text="Kill Process..."/> 
          </items> 
        </ContextMenu> 
      </contextMenu> 
    </TableView> 

Here, we are adding a contextMenu child to our TableView. Much like its sibling, MenuBar, contextMenu has an items child, which, in turn, has 0 or more MenuItem children. In this case, the MenuItem for Kill Process... looks remarkably like that under File, with the only difference being the mnemonicProcessing information. We're even reusing the ActionEvent handler, so there's no extra coding, and the behavior for killing a process is always the same, regardless of which menu item you click on.

主站蜘蛛池模板: 长葛市| 拉孜县| 新建县| 林西县| 常州市| 定襄县| 开江县| 东方市| 襄汾县| 武功县| 滨州市| 策勒县| 高台县| 山丹县| 十堰市| 苗栗县| 玉田县| 海丰县| 灌云县| 于都县| 清苑县| 洛扎县| 喀喇沁旗| 准格尔旗| 仁化县| 夹江县| 湟源县| 乌拉特中旗| 北宁市| 福泉市| 大冶市| 长治县| 台江县| 定州市| 子长县| 平塘县| 湘潭市| 舞钢市| 个旧市| 兴仁县| 隆子县|