1. 程式人生 > >JavaFX UI控制元件教程(二十三)之Menu

JavaFX UI控制元件教程(二十三)之Menu

翻譯自  Menu

本章介紹如何建立選單和選單欄,新增選單項,將選單分組,建立子選單以及設定上下文選單。

您可以使用以下JavaFX API類在JavaFX應用程式中構建選單。

  • 選單欄

  • 選單項

    • 選單

    • CheckMenuItem

    • RadioMenuItem

    • 選單

    • CustomMenuItem

      • SeparatorMenuItem

  • 上下文選單

圖22-1顯示了具有典型選單欄的應用程式的螢幕截圖。

圖22-1使用選單欄和三個選單類別的應用程式

 

在JavaFX應用程式中構建選單

選單是可以根據使用者的請求顯示的可操作專案列表。當選單可見時,使用者可以選擇一個選單項。使用者單擊某個專案後,選單將返回隱藏模式。通過使用選單,您可以通過在選單中放置並不總是需要顯示的功能來節省應用程式使用者介面(UI)中的空間。

選單欄中的選單通常按類別分組。編碼模式是宣告選單欄,定義類別選單,並使用選單項填充類別選單。在JavaFX應用程式中構建選單時,請使用以下選單項類:

  • MenuItem - 建立一個可操作的選項

  • Menu - 建立子選單

  • RadioButtonItem

     - 建立互斥選擇

  • CheckMenuItem - 建立可在選定和未選定狀態之間切換的選項

要分隔一個類別中的選單項,請使用SeparatorMenuItem該類。

按選單欄中的類別組織的選單通常位於視窗的頂部,剩下的場景用於關鍵的UI元素。如果出於某些原因,您無法為選單欄分配UI的任何可視部分,則可以使用使用者開啟的上下文選單,只需單擊滑鼠即可。

建立選單欄

雖然選單欄可以放在使用者介面的其他位置,但通常它位於UI的頂部,並且它包含一個或多個選單。選單欄會自動調整大小以適合應用程式視窗的寬度。預設情況下,新增到選單欄的每個選單都由帶有文字值的按鈕表示。

考慮一個呈現有關植物的參考資訊的應用程式,例如它們的名稱,二項式名稱,圖片和簡要描述。您可以建立三個選單類別:檔案,編輯和檢視,並使用選單項填充它們。例22-1顯示了添加了選單欄的此類應用程式的原始碼。

示例22-1選單示例應用程式

import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Effect;
import javafx.scene.effect.Glow;
import javafx.scene.effect.SepiaTone;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
 
public class MenuSample extends Application {
 
    final PageData[] pages = new PageData[] {
        new PageData("Apple",
            "The apple is the pomaceous fruit of the apple tree, species Malus "
            + "domestica in the rose family (Rosaceae). It is one of the most "
            + "widely cultivated tree fruits, and the most widely known of "
            + "the many members of genus Malus that are used by humans. "
            + "The tree originated in Western Asia, where its wild ancestor, "
            + "the Alma, is still found today.",
            "Malus domestica"),
        new PageData("Hawthorn",
            "The hawthorn is a large genus of shrubs and trees in the rose "
            + "family, Rosaceae, native to temperate regions of the Northern "
            + "Hemisphere in Europe, Asia and North America. "
            + The name hawthorn was "
            + "originally applied to the species native to northern Europe, "
            + "especially the Common Hawthorn C. monogyna, and the unmodified "
            + "name is often so used in Britain and Ireland.",
            "Crataegus monogyna"),
        new PageData("Ivy",
            "The ivy is a flowering plant in the grape family (Vitaceae) native to
            + " eastern Asia in Japan, Korea, and northern and eastern China. "
            + "It is a deciduous woody vine growing to 30 m tall or more given "
            + "suitable support,  attaching itself by means of numerous small "
            + "branched tendrils tipped with sticky disks.",
            "Parthenocissus tricuspidata"),
        new PageData("Quince",
            "The quince is the sole member of the genus Cydonia and is native to "
            + "warm-temperate southwest Asia in the Caucasus region. The "
            + "immature fruit is green with dense grey-white pubescence, most "
            + "of which rubs off before maturity in late autumn when the fruit "
            + "changes color to yellow with hard, strongly perfumed flesh.",
            "Cydonia oblonga")
    };
 
    final String[] viewOptions = new String[] {
        "Title", 
        "Binomial name", 
        "Picture", 
        "Description"
    };
 
    final Entry<String, Effect>[] effects = new Entry[] {
        new SimpleEntry<String, Effect>("Sepia Tone", new SepiaTone()),
        new SimpleEntry<String, Effect>("Glow", new Glow()),
        new SimpleEntry<String, Effect>("Shadow", new DropShadow())
    };
 
    final ImageView pic = new ImageView();
    final Label name = new Label();
    final Label binName = new Label();
    final Label description = new Label();
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        stage.setTitle("Menu Sample");
        Scene scene = new Scene(new VBox(), 400, 350);
        scene.setFill(Color.OLDLACE);
 
        MenuBar menuBar = new MenuBar();
 
        // --- Menu File
        Menu menuFile = new Menu("File");
 
        // --- Menu Edit
        Menu menuEdit = new Menu("Edit");
 
        // --- Menu View
        Menu menuView = new Menu("View");
 
        menuBar.getMenus().addAll(menuFile, menuEdit, menuView);
 
 
        ((VBox) scene.getRoot()).getChildren().addAll(menuBar);
 
        stage.setScene(scene);
        stage.show();
    }
 
 
    private class PageData {
        public String name;
        public String description;
        public String binNames;
        public Image image;
        public PageData(String name, String description, String binNames) {
            this.name = name;
            this.description = description;
            this.binNames = binNames;
            image = new Image(getClass().getResourceAsStream(name + ".jpg"));
        }
    }
}

與其他UI控制元件不同,Menu類的類和其他擴充套件MenuItem不會擴充套件Node類。它們無法直接新增到應用程式場景中,並且在通過該getMenus方法新增到選單欄之前保持不可見。

圖22-2選單欄已新增到應用程式中

您可以使用鍵盤的箭頭鍵瀏覽選單。但是,當您選擇選單時,不執行任何操作,因為尚未定義選單的行為。

 

新增選單項

通過新增以下項來設定“檔案”選單的功能:

  • 隨機播放 - 載入有關植物的參考資訊

  • 清除 - 刪除參考資訊並清除場景

  • 分隔符 - 分離選單項

  • 退出 - 退出應用程式

例22-2中的粗線通過使用MenuItem類建立了一個Shuffle選單,並將圖形元件新增到應用程式場景中。該MenuItem級能夠建立帶有文字和圖形的動作項。使用者單擊執行的操作由setOnAction方法定義,類似於Button類。

示例22-2使用Graphics新增Shuffle選單項

import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Effect;
import javafx.scene.effect.Glow;
import javafx.scene.effect.SepiaTone;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
 
public class MenuSample extends Application {
 
    final PageData[] pages = new PageData[] {
        new PageData("Apple",
            "The apple is the pomaceous fruit of the apple tree, species Malus "
            +"domestica in the rose family (Rosaceae). It is one of the most "
            +"widely cultivated tree fruits, and the most widely known of "
            +"the many members of genus Malus that are used by humans. "
            +"The tree originated in Western Asia, where its wild ancestor, "
            +"the Alma, is still found today.",
            "Malus domestica"),
        new PageData("Hawthorn",
            "The hawthorn is a large genus of shrubs and trees in the rose "
            + "family, Rosaceae, native to temperate regions of the Northern "
            + "Hemisphere in Europe, Asia and North America. "
            + "The name hawthorn was "
            + "originally applied to the species native to northern Europe, "
            + "especially the Common Hawthorn C. monogyna, and the unmodified "
            + "name is often so used in Britain and Ireland.",
            "Crataegus monogyna"),
        new PageData("Ivy",
            "The ivy is a flowering plant in the grape family (Vitaceae) native"
            +" to eastern Asia in Japan, Korea, and northern and eastern China."
            +" It is a deciduous woody vine growing to 30 m tall or more given "
            +"suitable support,  attaching itself by means of numerous small "
            +"branched tendrils tipped with sticky disks.",
            "Parthenocissus tricuspidata"),
        new PageData("Quince",
            "The quince is the sole member of the genus Cydonia and is native"
            +" to warm-temperate southwest Asia in the Caucasus region. The "
            +"immature fruit is green with dense grey-white pubescence, most "
            +"of which rubs off before maturity in late autumn when the fruit "
            +"changes color to yellow with hard, strongly perfumed flesh.",
            "Cydonia oblonga")
    };
 
    final String[] viewOptions = new String[] {
        "Title", 
        "Binomial name", 
        "Picture", 
        "Description"
    };
 
    final Entry<String, Effect>[] effects = new Entry[] {
        new SimpleEntry<String, Effect>("Sepia Tone", new SepiaTone()),
        new SimpleEntry<String, Effect>("Glow", new Glow()),
        new SimpleEntry<String, Effect>("Shadow", new DropShadow())
    };
 
    final ImageView pic = new ImageView();
    final Label name = new Label();
    final Label binName = new Label();
    final Label description = new Label();
    private int currentIndex = -1;
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        stage.setTitle("Menu Sample");
        Scene scene = new Scene(new VBox(), 400, 350);
        scene.setFill(Color.OLDLACE);
 
        name.setFont(new Font("Verdana Bold", 22));
        binName.setFont(new Font("Arial Italic", 10));
        pic.setFitHeight(150);
        pic.setPreserveRatio(true);
        description.setWrapText(true);
        description.setTextAlignment(TextAlignment.JUSTIFY);
 
        shuffle();
 
        MenuBar menuBar = new MenuBar();
 
        final VBox vbox = new VBox();
        vbox.setAlignment(Pos.CENTER);
        vbox.setSpacing(10);
        vbox.setPadding(new Insets(0, 10, 0, 10));
        vbox.getChildren().addAll(name, binName, pic, description);
 
        // --- Menu File
        Menu menuFile = new Menu("File");
        MenuItem add = new MenuItem("Shuffle",
            new ImageView(new Image("menusample/new.png")));
        add.setOnAction(new EventHandler<ActionEvent>() {
            public void handle(ActionEvent t) {
                shuffle();
                vbox.setVisible(true);
            }
        });        
 
        menuFile.getItems().addAll(add);
 
        // --- Menu Edit
        Menu menuEdit = new Menu("Edit");
        
        // --- Menu View
        Menu menuView = new Menu("View");
        
        menuBar.getMenus().addAll(menuFile, menuEdit, menuView);
        ((VBox) scene.getRoot()).getChildren().addAll(menuBar, vbox);
        stage.setScene(scene);
        stage.show();
    }
 
    private void shuffle() {
        int i = currentIndex;
        while (i == currentIndex) {
            i = (int) (Math.random() * pages.length);
        }
        pic.setImage(pages[i].image);
        name.setText(pages[i].name);
        binName.setText("(" + pages[i].binNames + ")");
        description.setText(pages[i].description);
        currentIndex = i;
    }
 
    
    private class PageData {
        public String name;
        public String description;
        public String binNames;
        public Image image;
        public PageData(String name, String description, String binNames) {
            this.name = name;
            this.description = description;
            this.binNames = binNames;
            image = new Image(getClass().getResourceAsStream(name + ".jpg"));
        }
    }
} 

當用戶選擇Shuffle選單項時,shuffle呼叫的方法setOnAction通過計算相應陣列中元素的索引來指定標題,二項式名稱,工廠圖片及其描述。

“清除”選單項用於擦除應用程式場景。您可以通過使VBox具有GUI元素的容器不可見來實現此操作,如例22-3所示。

示例22-3使用加速器建立清除選單項

MenuItem clear = new MenuItem("Clear");
clear.setAccelerator(KeyCombination.keyCombination("Ctrl+X"));
clear.setOnAction(new EventHandler<ActionEvent>() {
        public void handle(ActionEvent t) {
            vbox.setVisible(false);
        }
});

在實施MenuItem類別可讓開發設定選單加速器,執行相同的動作的選單項的組合鍵。使用“清除”選單,使用者可以從“檔案”選單類別中選擇操作,也可以同時按下“控制鍵”和“X”鍵。

Exit選單關閉應用程式視窗。設定System.exit(0)為此選單項的操作,如例22-4所示。

示例22-4建立退出選單項

MenuItem exit = new MenuItem("Exit");
exit.setOnAction(new EventHandler<ActionEvent>() {
    public void handle(ActionEvent t) {
        System.exit(0);
    }
});

使用示例22-5中getItems顯示的方法將新建立的選單項新增到“檔案”選單。您可以建立一個分隔符選單項並將其新增到方法中,以便可視地分離“退出”選單項。getItems

示例22-5新增選單項

menuFile.getItems().addAll(add, clear, new SeparatorMenuItem(), exit);

示例22-2示例22-3示例22-4示例22-5新增到Menu Sample應用程式,然後編譯並執行它。選擇Shuffle選單項以載入有關不同植物的參考資訊。然後清除場景(清除),並關閉應用程式(退出)。圖22-3顯示了Clear選單項的選擇。

圖22-3包含三個選單項的檔案選單

使用“檢視”選單,您可以隱藏和顯示參考資訊的元素。實現createMenuItem方法並在方法中呼叫它start以建立四個CheckMenuItem物件。然後將新建立的檢查選單項新增到“檢視”選單,以切換標題,二項式名稱,工廠圖片及其描述的可見性。例22-6顯示了實現這些任務的兩個程式碼片段。

示例22-6應用CheckMenuItem類建立切換選項

// --- Creating four check menu items within the start method
CheckMenuItem titleView = createMenuItem ("Title", name);                                                       
CheckMenuItem binNameView = createMenuItem ("Binomial name", binName);        
CheckMenuItem picView = createMenuItem ("Picture", pic);        
CheckMenuItem descriptionView = createMenuItem ("Description", description);     
menuView.getItems().addAll(titleView, binNameView, picView, descriptionView);

...

// The createMenuItem method
private static CheckMenuItem createMenuItem (String title, final Node node){
    CheckMenuItem cmi = new CheckMenuItem(title);
    cmi.setSelected(true);
    cmi.selectedProperty().addListener(new ChangeListener<Boolean>() {
        public void changed(ObservableValue ov,
        Boolean old_val, Boolean new_val) {
            node.setVisible(new_val);
        }
    });
    return cmi;
}

CheckMenuItem班是的擴充套件MenuItem類。它可以在選定和取消選擇的狀態之間切換。選中後,檢查選單項會顯示覆選標記。

例22-6建立了四個CheckMenuItem物件並處理其selectedProperty屬性的更改。例如,當用戶取消選擇該項時picView,該setVisible方法接收該false值,該工廠的圖片變得不可見。將此程式碼片段新增到應用程式,編譯並執行應用程式時,您可以嘗試選擇和取消選擇選單項。圖22-4顯示了顯示工廠標題和圖片時的應用程式,但隱藏了其二項式名稱和描述。

圖22-4使用檢查選單項

 

建立子選單

對於“編輯”選單,定義兩個選單項:“圖片效果”和“無效果”。“圖片效果”選單項被設計為具有三個專案的子選單,用於設定三種可用視覺效果中的一種。“無效果”選單項將刪除所選效果並恢復影象的初始狀態。

使用RadioMenuItem該類建立子選單的專案。將單選選單按鈕新增到切換組以使選擇互斥。例22-7實現了這些任務。

示例22-7使用單選選單項建立子選單

//Picture Effect menu
Menu menuEffect = new Menu("Picture Effect");
final ToggleGroup groupEffect = new ToggleGroup();
for (Entry<String, Effect> effect : effects) {
    RadioMenuItem itemEffect = new RadioMenuItem(effect.getKey());
    itemEffect.setUserData(effect.getValue());
    itemEffect.setToggleGroup(groupEffect);
    menuEffect.getItems().add(itemEffect);
}
//No Effects menu
final MenuItem noEffects = new MenuItem("No Effects");

 noEffects.setOnAction(new EventHandler<ActionEvent>() {
     public void handle(ActionEvent t) {
         pic.setEffect(null);
         groupEffect.getSelectedToggle().setSelected(false);
     }
});

//Processing menu item selection
groupEffect.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
    public void changed(ObservableValue<? extends Toggle> ov,
        Toggle old_toggle, Toggle new_toggle) {
            if (groupEffect.getSelectedToggle() != null) {
                Effect effect = 
                    (Effect) groupEffect.getSelectedToggle().getUserData();
                pic.setEffect(effect);
            }
        }
 });
//Adding items to the Edit menu
menuEdit.getItems().addAll(menuEffect, noEffects);

setUserData方法定義特定無線電選單項的視覺效果。當選擇切換組中的一個專案時,相應的效果將應用於圖片。選擇“無效果”選單項時,該setEffect方法指定該null值,並且不對圖片應用任何效果。

圖22-5捕獲使用者選擇Shadow選單項的時刻。

圖22-5帶有三個單選選單項的子選單

DropShadow效果被施加到影象,它看起來如圖圖22-6

圖22-6應用了DropShadow效果的Quince圖片

在“圖片效果”子選單中未選擇任何效果時,可以使用類的setDisable方法MenuItem禁用“無效果”選單。修改例22-7,如例22-8所示。

示例22-8禁用選單項

Menu menuEffect = new Menu("Picture Effect");
final ToggleGroup groupEffect = new ToggleGroup();
for (Entry<String, Effect> effect : effects) {
     RadioMenuItem itemEffect = new RadioMenuItem(effect.getKey());
     itemEffect.setUserData(effect.getValue());
     itemEffect.setToggleGroup(groupEffect);
     menuEffect.getItems().add(itemEffect);
}
final MenuItem noEffects = new MenuItem("No Effects");
noEffects.setDisable(true);      
noEffects.setOnAction(new EventHandler<ActionEvent>() {
    public void handle(ActionEvent t) {
        pic.setEffect(null);
        groupEffect.getSelectedToggle().setSelected(false);
        noEffects.setDisable(true);
    }
});

groupEffect.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
    public void changed(ObservableValue<? extends Toggle> ov,
        Toggle old_toggle, Toggle new_toggle) {
            if (groupEffect.getSelectedToggle() != null) {
                Effect effect = 
                    (Effect) groupEffect.getSelectedToggle().getUserData();
                pic.setEffect(effect);
                noEffects.setDisable(false);
            } else {
                noEffects.setDisable(true);
        }
    }
});
menuEdit.getItems().addAll(menuEffect, noEffects);

如果未RadioMenuItem選擇任何選項,則禁用“無效果”選單項,如圖22-7所示。當用戶選擇其中一個視覺效果時,將啟用“無效果”選單項。

圖22-7效果選單項被禁用

 

新增上下文選單

如果無法為所需功能分配使用者介面的任何空間,則可以使用上下文選單。上下文選單是一個彈出視窗,響應滑鼠單擊而顯示。上下文選單可以包含一個或多個選單項。

在“選單示例”應用程式中,為工廠的圖片設定上下文選單,以便使用者可以複製影象。

使用ContextMenu該類定義上下文選單,如例22-9所示。

示例22-9定義上下文選單

final ContextMenu cm = new ContextMenu();
MenuItem cmItem1 = new MenuItem("Copy Image");
cmItem1.setOnAction(new EventHandler<ActionEvent>() {
    public void handle(ActionEvent e) {
        Clipboard clipboard = Clipboard.getSystemClipboard();
        ClipboardContent content = new ClipboardContent();
        content.putImage(pic.getImage());
        clipboard.setContent(content);
    }
});

cm.getItems().add(cmItem1);
pic.addEventHandler(MouseEvent.MOUSE_CLICKED,
    new EventHandler<MouseEvent>() {
        @Override public void handle(MouseEvent e) {
            if (e.getButton() == MouseButton.SECONDARY)  
                cm.show(pic, e.getScreenX(), e.getScreenY());
        }
});

當用戶右鍵單擊該ImageView物件時,將show呼叫該方法以使其顯示上下文選單。

setOnAction為上下文選單的“複製影象”項定義的方法會建立一個Clipboard物件,並將影象新增為其內容。圖22-8捕獲使用者選擇“複製影象”上下文選單項的時刻。

圖22-8使用上下文選單

您可以嘗試複製影象並將其貼上到圖形編輯器中。

要進一步增強,可以向上下文選單新增更多選單項並指定不同的操作。您還可以使用CustomMenuItem該類建立自定義選單。使用此類,您可以在選單中嵌入任意節點,並指定例如按鈕或滑塊作為選單項。

 

相關的API文件 

</