有一天,我想到編寫一個小型桌面應用程式來滿足我的需求 - 就像一本用於學習外語單字的小字典 - 我開始絞盡腦汁,我該怎麼做呢?自然,我第一個想到的就是Swing。想必大家都聽過Swing。這是一個用於建立使用者圖形介面的庫。由於我們心愛的 Oracle 還沒有完全放棄 Swing,所以它並沒有被認為是過時的,應用程式仍然在其上運行。然而,Swing 不再對其進行現代化改造,Oracle 的人員已經讓我們領略了 JavaFX 的未來。而事實上,JavaFX 使用 Swing 元件作為服務提供者)
(有點像登記狗的窗口)
當您選擇一隻狗並按刪除按鈕時,該狗就會從我們的清單中刪除。當您選擇一個四足朋友並更改其欄位時,按下「編輯」按鈕後,狗的資訊就會更新。當我們按下“新建”按鈕時,會彈出一個窗口,為新狗創建一條記錄(從它的名字開始): 然後單擊“保存”並在第一個窗口中填寫其其餘字段,然後單擊“編輯”按鈕節省。聽起來很容易,對吧?讓我們看看這在我們的 Java 應用程式中會是什麼樣子。首先,我將在以下位置保留生成的這兩個視窗的 xml 佈局
這可能就是我今天的全部內容了。今天我們簡單地了解了基本概念和使用 JavaFX 的示例,並且我們可以構建小型桌面應用程式(使用附加信息,幸運的是,互聯網上有豐富的信息)。而你,反過來,喜歡))
什麼是 JavaFX?
JavaFX 本質上是一個 Java 的 GUI 工具包。這裡有一個小題外話,我們會記住GUI是什麼: 圖形用戶界面- 圖形用戶界面是一種用戶界面,其中所有元素(按鈕、菜單、圖標、列表)在界面上呈現給用戶展示以圖片、圖形的形式進行。與命令列介面不同,在 GUI 中,使用者可以使用輸入設備隨機存取可見物件。通常,介面元素以隱喻的形式實現,並顯示其屬性和目的以方便使用者理解。 JavaFX旨在用 Java 創建遊戲和桌面應用程式。事實上,由於針對 Java 提出了新的 GUI 工具,它將取代 Swing。此外,它還允許我們使用 CSS 設定 GUI 佈局檔案 (XML) 的樣式並使其更加優雅,類似於我們在 Web 應用程式中所習慣的。JavaFX 還可以在單一 GUI 工具包中處理整合的 3D 圖形以及音訊、視訊和嵌入式網路應用程式...它易於學習且經過良好優化。它支援許多作業系統,以及Windows、UNIX 系統和Mac OS。JavaFX 特點:
- JavaFX最初附帶了大量的圖形介面部分,例如各種按鈕、文字欄位、表格、樹、選單、圖表等,這反過來又節省我們大量的時間。
- JavaFX 經常使用 CSS 樣式,我們將能夠使用特殊的 FXML 格式來建立 GUI,而不是使用 Java 程式碼。這使得快速佈局 GUI 或更改外觀或組成變得容易,而無需長時間使用 Java 程式碼。
- JavaFX 具有現成的圖表部分,因此當您需要基本圖表時,我們不必從頭開始編寫它們。
- JavaFX 另外還提供 3D 圖形支持,如果我們正在開發某種遊戲或類似應用程序,這通常很有用。
- 舞臺本質上是一個周圍的窗口,充當起始畫布並包含其餘組件。一個應用程式可以有多個階段,但無論如何都必須有一個這樣的元件。本質上,Stage 是主要容器和入口點。
- 場景- 顯示舞台的內容(如嵌套娃娃)。每個階段可以包含多個元件 - 場景,它們可以在它們之間切換。在內部,這是透過稱為場景圖的物件圖來實現的(其中每個元素都是一個節點,也稱為Node)。
- 節點是控件,例如標籤按鈕,甚至佈局,它們內部可以有多個巢狀元件。每個場景可以有一個巢狀節點,但它可以是具有多個元件的佈局。嵌套可以是多層的,佈局包含其他佈局和常規組件。每個這樣的節點都有自己的識別碼、樣式、效果、狀態和事件處理程序。
JavaFX:使用範例
我們建立一個帶有方法main
(入口點)的常規類別:
public class AppFX extends Application {
public static void main(String[] args) {
Application.launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
pimarySatge.show();
}
}
這裡我們的類別繼承自javafx.application.Application
(我們從 Bugaga 盒子中繼承)。在 main 中,我們呼叫靜態 Application 方法launch()
來啟動我們的視窗。另外,我們的想法會抱怨我們沒有實作 Application 方法,start
而這正是我們最終要做的。它是做什麼用的?並且為了能夠管理屬性(我們視窗的功能)。為此,我們使用傳入參數primaryStage
來呼叫方法show
,以便我們可以看到正在啟動的視窗main
。讓我們稍微填寫一下我們的方法start
:
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Dogs application");
primaryStage.setWidth(500);
primaryStage.setHeight(400);
InputStream iconStream =
getClass().getResourceAsStream("/images/someImage.png");
Image image = new Image(iconStream);
primaryStage.getIcons().add(image);
Button button = new Button("WOF WOF ???'");
button.setOnAction(e -< {
Alert alert = new Alert(Alert.AlertType.INFORMATION, "WOF WOF WOF!!!");
alert.showAndWait();
});
Scene primaryScene = new Scene(button);
primaryStage.setScene(primaryScene);
primaryStage.show();
}
那麼我們在這裡看到了什麼?讓我們逐行瀏覽一下: 2 - 設定視窗本身的名稱(階段) 3.4 - 設定其尺寸 6.7 - 設定讀取流到檔案的路徑(圖示) 8 - 將檔案建立為 Image 對象,它透過建構函數中傳遞的流連接到真實文件9 - 在視窗的頂部面板中設定一個圖示11 - 建立一個按鈕物件13-16 - 設定按下按鈕時的反應17 - 建立一個場景,其中我們放置按鈕18 - 將場景放置在公共視窗上20 - 設定視窗的可見性標誌結果我們得到一個小視窗來歡迎我們最喜歡的pesels: 一切看起來都比 Swing 簡單得多,不是嗎?但事情還沒結束。編寫所有程式碼來顯示應用程式並不好;您需要以某種方式對其進行劃分,以便使其更容易理解(圖形組件放在一個籃子中,邏輯放在另一個籃子中)。這裡 xml 出現了... 天啊,xml?確切地。具體來說,我們使用 JavaFX - FXML 的特定實現,在其中定義應用程式的圖形元件及其屬性(各種大小等),然後將其連接到控制器,這有助於管理邏輯。讓我們來看看這個 xml 的範例:
<?xml version="1.0" encoding="UTF-8"?>
<?language javascript?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
id="Dogs application" prefHeight="200" prefWidth="300" alignment="center">
<Label text="Wow wow?"/>
<Button fx:id="mainButton" text="Greeting" onAction="buttonClicked()"/>
<fx:script>
function buttonClicked() {
mainButton.setText("Wow wow wow!!!")
}
</fx:script>
</VBox>
2 - 我們使用的腳本語言 4-6 - 匯入的資料 8-9 Vbox - 將子元件放置在一行上的容器。11 - 顯示一些文字 13 - 一個按鈕,點擊時,我們使用腳本中第15-18行描述的方法,方法中應該有調用這個xml檔案的程式碼start
,但現在這不是那麼重要了,我們將省略它(下面將有一個拉取此文件的範例)。所以,xml當然好(但不是很好),手動編寫它們很混亂,這不是上個世紀了嗎?
JavaFX SceneBuilder 簡介
正是在這一點上(鼓聲)開始發揮作用 -SceneBuilder
在 JavaFX Scene Builder中,我們可以使用它以圖形界面的形式設計窗口,然後保存它們,並且該程式將根據結果,將構建我們將在我們的應用程式中改進它的xml 檔案。這個 fmxl 建構器的介面如下所示:
一個小題外話。JavaFX 課程
我將跳過安裝細節,並詳細研究這個工具。這些都是值得進一步探討的議題。因此,我仍然會留下一些有趣的 JavaFX 課程連結:一個(JavaFX 線上教學)和兩個(另一個很好的教學)。讓我們來看看我草擬的一個小例子。最後我得到了類似的東西:SceneBuilder
: 首先(基本):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<AnchorPane prefHeight="300.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.tutorial.controller.BaseController">
<children>
<SplitPane dividerPositions="0.29797979797979796" prefHeight="300.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<TableView fx:id="dogs" layoutX="-2.0" layoutY="-4.0" prefHeight="307.0" prefWidth="190.0" AnchorPane.bottomAnchor="-5.0" AnchorPane.leftAnchor="-2.0" AnchorPane.rightAnchor="-13.0" AnchorPane.topAnchor="-4.0">
<columns>
<TableColumn fx:id="nameList" prefWidth="100.33334350585938" text="Nickname" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<Label layoutX="49.0" layoutY="25.0" text="Person Details" AnchorPane.leftAnchor="5.0" AnchorPane.topAnchor="5.0" />
<GridPane accessibleText="erreererer" gridLinesVisible="true" layoutX="5.0" layoutY="31.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="31.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label prefHeight="17.0" prefWidth="70.0" text="Nickname" />
<Label text="Breed" GridPane.rowIndex="1" />
<Label text="Age" GridPane.rowIndex="2" />
<Label text="City" GridPane.rowIndex="3" />
<Label text="Level of training" GridPane.rowIndex="4" />
<TextField fx:id="breed" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<TextField fx:id="age" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<TextField fx:id="city" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<TextField fx:id="levelOfTraining" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<TextField fx:id="name" GridPane.columnIndex="1" />
</children>
</GridPane>
<Button layoutX="251.0" layoutY="259.0" mnemonicParsing="false" onAction="#create" text="New" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="230.0" AnchorPane.rightAnchor="130.0" AnchorPane.topAnchor="260.0" />
<Button layoutX="316.0" layoutY="262.0" mnemonicParsing="false" onAction="#edit" text="Edit" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="290.0" AnchorPane.rightAnchor="70.0" AnchorPane.topAnchor="260.0" />
<Button layoutX="360.0" layoutY="262.0" mnemonicParsing="false" onAction="#delete" text="Delete" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="350.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="260.0" />
</children>
</AnchorPane>
</items>
</SplitPane>
</children>
</AnchorPane>
第二(創造新的小狗):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="200.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.tutorial.controller.NewDogController">
<children>
<GridPane layoutX="31.0" layoutY="25.0" prefHeight="122.0" prefWidth="412.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="185.0" minWidth="10.0" prefWidth="149.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="173.0" minWidth="10.0" prefWidth="146.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label prefHeight="48.0" prefWidth="178.0" text="Please, write name:">
<font>
<Font size="20.0" />
</font>
</Label>
<TextField fx:id="nickName" prefHeight="36.0" prefWidth="173.0" GridPane.columnIndex="1" />
</children>
</GridPane>
<Button layoutX="222.0" layoutY="149.0" mnemonicParsing="false" onAction="#ok" prefHeight="37.0" prefWidth="95.0" text="Save" />
<Button layoutX="325.0" layoutY="149.0" mnemonicParsing="false" onAction="#cansel" prefHeight="37.0" prefWidth="95.0" text="Cansel" />
</children>
</AnchorPane>
資料夾結構是什麼樣的: 如您所見,沒有什麼特別的,有代表某些視窗的控制器,有代表我們資料的模型。讓我們來看看啟動應用程式的類別(應用程式實作): @Data
public class AppFX extends Application {
private Stage primaryStage;
private AnchorPane rootLayout;
private ObservableList listDog = FXCollections.observableArrayList();
public AppFX() {
listDog.add(new Dog("Fluffy", "Pug", 8, "Odessa", 2));
listDog.add(new Dog("Archie", "Poodle", 3, "Lviv", 6));
listDog.add(new Dog("Willie", "Bulldog", 5, "Kiev", 4));
listDog.add(new Dog("Hector", "Shepherd", 9, "Minsk", 6));
listDog.add(new Dog("Duncan", "Dachshund", 1, "Hogwarts", 9));
}
在這裡,我們看到一個建構函數,它將填充我們的初始資料(我們將其儲存在一個特殊的表中 - ObservableList)。
public static void main(String[] args) {
Application.launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Dogs application");
showBaseWindow();
}
沒什麼特別的 -以及啟動應用程式的 main
實現:start()
public void showBaseWindow() {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(AppFX.class.getResource("/maket/rootWindow.fxml"));
rootLayout = loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
InputStream iconStream = getClass().getResourceAsStream("/icons/someImage.png");
Image image = new Image(iconStream);
primaryStage.getIcons().add(image);
BaseController controller = loader.getController();
controller.setAppFX(this);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
因此,在這裡我們看到了實際啟動的方法start()
,即設定基本視窗設定的方法。例如在資源中的xml佈局中:給它一個圖標,將它連結到特定的控制器,並給控制器一個到this
類別的連結)
public void showCreateWindow(Dog dog) {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(AppFX.class.getResource("/maket/new.fxml"));
AnchorPane page = loader.load();
Stage dialogStage = new Stage();
dialogStage.setTitle("Wow Wow Wow");
dialogStage.initModality(Modality.WINDOW_MODAL);
dialogStage.initOwner(primaryStage);
dialogStage.setScene(new Scene(page));
CreateController controller = loader.getController();
controller.setDialogStage(dialogStage);
controller.setDog(dog);
dialogStage.showAndWait();
} catch (IOException e) {
e.printStackTrace();
}
}
}
這裡我們看到負責第二個視窗出現的方法-用來建立新記錄(新狗的名字)的視窗。我們還設定控制器、xml 佈局、舞台等...我們將考慮的下一個類別將是代表我們的狗的模型(有關它的資訊): @Data
public class Dog {
private StringProperty name;
private StringProperty breed;
private IntegerProperty age;
private StringProperty city;
private IntegerProperty levelOfTraining;
public Dog(String name, String breed, int age, String city, int levelOfTraining) {
this.name = new SimpleStringProperty(name);
this.breed = new SimpleStringProperty(breed);
this.age = new SimpleIntegerProperty(age);
this.city = new SimpleStringProperty(city);
this.levelOfTraining = new SimpleIntegerProperty(levelOfTraining);
}
public Dog() {
name = new SimpleStringProperty();
breed = null;
age = null;
city = null;
levelOfTraining = null;
}
}
這裡我們看到兩個建構函式。一個是幾乎帶有所有參數的常規建構函式(幾乎是因為我們使用簡單型別的特殊FX 包裝器),另一個是不帶參數的建構子:我們在建立一隻新狗時使用它,它一開始只有一個姓名。基本視窗控制器: @Data
public class BaseController {
@FXML
private TableView dogs;
@FXML
private TableColumn nameList;
@FXML
private TextField name;
@FXML
private TextField breed;
@FXML
private TextField age;
@FXML
private TextField city;
@FXML
private TextField levelOfTraining;
private AppFX appFX;
在這裡我們看到我們的物件字段,但採用 TextField 格式。這是表示文字輸入欄位的格式。@FXML 是一個註釋,旨在連結 Java 程式碼和佈局的相應物件(按鈕、欄位或其他內容)。
@FXML
private void initialize() {
nameList.setCellValueFactory(
cellData -> cellData.getValue().getName());
dogs.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> showDogsInformation(newValue));
}
在這裡,我們在清單的右側看到一個用於顯示狗名稱的方法(其 @FXML 註解綁定到 JavaFX TableView 版面配置元件)。
public void setAppFX(AppFX appFX) {
this.appFX = appFX;
dogs.setItems(appFX.getListDog());
}
private void showDogsInformation(Dog dog) {
if (dog != null) {
name.setText(dog.getName() != null ? dog.getName().getValue() : null);
breed.setText(dog.getBreed() != null ? dog.getBreed().getValue() : null);
age.setText(dog.getAge() != null ? String.valueOf(dog.getAge().get()) : null);
city.setText(dog.getCity() != null ? dog.getCity().getValue() : null);
levelOfTraining.setText(dog.getLevelOfTraining() != null ? String.valueOf(dog.getLevelOfTraining().get()) : null);
} else {
name.setText("");
breed.setText("");
age.setText("");
city.setText("");
levelOfTraining.setText("");
}
}
在第一個方法中,我們看到設定對實作 Application 的類別的內部引用(以便我們可以呼叫其方法來呼叫第二個視窗),並設定要顯示的初始清單。第二個檢查當前狗是否具有某些數據,並基於此設定文字欄位:
@FXML
private void delete() {
int selectedIndex = dogs.getSelectionModel().getSelectedIndex();
dogs.getItems().remove(selectedIndex);
}
@FXML
private void edit() {
int selectedIndex = dogs.getSelectionModel().getSelectedIndex();
dogs.getItems().set(selectedIndex, new Dog(name.getText(), breed.getText(), Integer.valueOf(age.getText()), city.getText(), Integer.valueOf(levelOfTraining.getText())));
}
@FXML
private void create() {
Dog someDog = new Dog();
appFX.showCreateWindow(someDog);
if (someDog.getName() != null && !someDog.getName().getValue().isEmpty()) {
appFX.getListDog().add(someDog);
}
}
}
這裡我們看到與按鈕關聯的基本視窗的三個方法:
- 刪除-按索引刪除選取的(選定的)狗;
- 編輯- 使用傳輸的數據創建一隻新狗,並將其設置為代替之前的狗;
- create - 我們建立一隻新狗,並呼叫呼叫建立視窗的方法,傳遞一個新對象,關閉該對象後,如果名稱不為空,則儲存新寵物。
@Data
public class CreateController {
private Stage dialogStage;
private Dog dog;
@FXML
private TextField nickName;
@FXML
private void ok() {
if (nickName != null && !nickName.getText().isEmpty()) {
dog.setName(new SimpleStringProperty(nickName.getText()));
dialogStage.close();
}
}
@FXML
private void cansel() {
dialogStage.close();
}
}
在這裡,我們看到與視窗中文字欄位的連接,處理「儲存」和「取消」按鈕,從而以某種方式關閉視窗。正如您所看到的,為了在我的小型應用程式中提供更大的便利,我使用了 Lombok,否則程式碼會成長很多,而且我不會將其納入我的審查中。
GO TO FULL VERSION