1. 程式人生 > >【JavaFx教程】第三部分:與使用者的互動

【JavaFx教程】第三部分:與使用者的互動

Screenshot AddressApp Part 3

第3部分的主題:

  1. 在表中反應選擇的改變(TableView中)。
  2. 增加增加編輯刪除按鈕的功能。
  3. 建立自定義彈出對話方塊編輯人員。
  4. 驗證使用者輸入

響應表的選擇

顯然,我們還沒有使用應用程式的右邊。想法是當用戶選擇表中的人員時,在右邊顯示人員的詳情。

首先,讓我們在PersonOverviewController新增一個新的方法,幫助我們使用單個人員的資料填寫標籤。

建立方法showPersonDetails(Person person)。遍歷所有標籤,並且使用setText(…)方法設定標籤的文字為個人的詳情。如果null作為引數傳遞,所有的標籤應該被清空。

PersonOverviewController.java

/**
 * Fills all text fields to show details about the person.
 * If the specified person is null, all text fields are cleared.
 * 
 * @param person the person or null
 */
private void showPersonDetails(Person person) {
    if (person != null) {
        // Fill the labels with info from the person object.
        firstNameLabel.setText(person.getFirstName());
        lastNameLabel.setText(person.getLastName());
        streetLabel.setText(person.getStreet());
        postalCodeLabel.setText(Integer.toString(person.getPostalCode()));
        cityLabel.setText(person.getCity());

        // TODO: We need a way to convert the birthday into a String! 
        // birthdayLabel.setText(...);
    } else {
        // Person is null, remove all the text.
        firstNameLabel.setText("");
        lastNameLabel.setText("");
        streetLabel.setText("");
        postalCodeLabel.setText("");
        cityLabel.setText("");
        birthdayLabel.setText("");
    }
}

轉換生日日期為字串

你注意到我們沒有設定birthday到標籤中,因為它是LocalDate型別,不是String。我們首先需要格式化日期。

在幾個地方上我們使用LocalDateString之間的轉換。好的實踐是建立一個帶有static方法的幫助類。我們稱它為DateUtil,並且把它放到單獨的包中,稱為ch.makery.address.util

DateUtil.java

package ch.makery.address.util;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

/**
 * Helper functions for handling dates.
 * 
 * @author Marco Jakob
 */
public class DateUtil {
    
    /** The date pattern that is used for conversion. Change as you wish. */
    private static final String DATE_PATTERN = "dd.MM.yyyy";
    
    /** The date formatter. */
    private static final DateTimeFormatter DATE_FORMATTER = 
            DateTimeFormatter.ofPattern(DATE_PATTERN);
    
    /**
     * Returns the given date as a well formatted String. The above defined 
     * {@link DateUtil#DATE_PATTERN} is used.
     * 
     * @param date the date to be returned as a string
     * @return formatted string
     */
    public static String format(LocalDate date) {
        if (date == null) {
            return null;
        }
        return DATE_FORMATTER.format(date);
    }

    /**
     * Converts a String in the format of the defined {@link DateUtil#DATE_PATTERN} 
     * to a {@link LocalDate} object.
     * 
     * Returns null if the String could not be converted.
     * 
     * @param dateString the date as String
     * @return the date object or null if it could not be converted
     */
    public static LocalDate parse(String dateString) {
        try {
            return DATE_FORMATTER.parse(dateString, LocalDate::from);
        } catch (DateTimeParseException e) {
            return null;
        }
    }

    /**
     * Checks the String whether it is a valid date.
     * 
     * @param dateString
     * @return true if the String is a valid date
     */
    public static boolean validDate(String dateString) {
        // Try to parse the String.
        return DateUtil.parse(dateString) != null;
    }
}

提示:你能通過改變DATE_PATTERN修改日期的格式。所有可能的格式參考 DateTimeFormatter.

使用DateUtil

現在,我們需要在PersonOverviewControllershowPersonDetails方法中使用我們新建的DateUtil。使用下面這樣替代我們新增的TODO

birthdayLabel.setText(DateUtil.format(person.getBirthday()));

監聽表選擇的改變

為了當用戶在人員表中選擇一個人時獲得通知,我們需要監聽改變

在JavaFX中有一個介面稱為ChangeListener,帶有一個方法changed()。該方法有三個引數:observableoldValuenewValue

我們使用*Java 8 lambda*表示式建立這樣一個ChangeListener。讓我們新增一些行到PersonOverviewControllerinitialize()方法中。現在看起來是這樣的。

PersonOverviewController.java

@FXML
private void initialize() {
    // Initialize the person table with the two columns.
    firstNameColumn.setCellValueFactory(
            cellData -> cellData.getValue().firstNameProperty());
    lastNameColumn.setCellValueFactory(
            cellData -> cellData.getValue().lastNameProperty());

    // Clear person details.
    showPersonDetails(null);

    // Listen for selection changes and show the person details when changed.
    personTable.getSelectionModel().selectedItemProperty().addListener(
            (observable, oldValue, newValue) -> showPersonDetails(newValue));
}

使用showPersonDetails(null),我們重設個人詳情。

使用personTable.getSelectionModel...,我們獲得人員表的selectedItemProperty,並且新增監聽。不管什麼時候使用者選擇表中的人員,都會執行我們的lambda表示式。我們獲取新選擇的人員,並且把它傳遞給showPersonDetails(...)方法。

現在試著執行你的應用程式,驗證當你選擇表中的人員時,關於該人員的詳情是否正確的顯示。

如果有些事情不能工作,你可以對比下PersonOverviewController.java中的PersonOverviewController

刪除按鈕

我們的使用者介面已經包含一個刪除按鈕,但是沒有任何功能。我們能在*SceneBuilder*中的按鈕上選擇動作。在我們控制器中的任何使用@FXML(或者它是公用的)註釋的方法都可以被*Scene Builder*訪問。因此,讓我們在PersonOverviewController類的最後新增一個刪除方法。

PersonOverviewController.java

/**
 * Called when the user clicks on the delete button.
 */
@FXML
private void handleDeletePerson() {
    int selectedIndex = personTable.getSelectionModel().getSelectedIndex();
    personTable.getItems().remove(selectedIndex);
}

現在,使用*SceneBuilder*開啟PersonOverview.fxml檔案,選擇*Delete*按鈕,開啟*Code*組,在On Actin的下拉選單中選擇handleDeletePerson

On Action

錯誤處理

如果你現在執行應用程式,你應該能夠從表中刪除選擇的人員。但是,當你沒有在表中選擇人員時點選刪除按鈕時會發生什麼呢

這裡有一個ArrayIndexOutOfBoundsException,因為它不能刪除掉索引為-1人員專案。索引-1由getSelectedIndex()返回,它意味著你沒有選擇專案。

當然,忽略這種錯誤不是非常好。我們應該讓使用者知道在刪除時必須選擇一個人員。(更好的是我們應該禁用刪除按鈕,以便使用者沒有機會做錯誤的事情)。

我們新增一個彈出對話方塊通知使用者,你將需要*新增一個庫Dialogs

  1. 在專案中建立一個lib子目錄,新增controlsf jar檔案到該目錄下。
  2. 新增庫到你的專案classpath中。在Eclipse中右擊jar檔案|選擇Build Path| *Add to Build Path*。現在Eclipse知道這個庫了。

ControlsFX Libaray

handleDeletePerson()方法做一些修改後,不管什麼時候使用者沒有選擇表中的人員時按下刪除按鈕,我們能顯示一個簡單的對話方塊。

PersonOverviewController.java

/**
 * Called when the user clicks on the delete button.
 */
@FXML
private void handleDeletePerson() {
    int selectedIndex = personTable.getSelectionModel().getSelectedIndex();
    if (selectedIndex >= 0) {
        personTable.getItems().remove(selectedIndex);
    } else {
        // Nothing selected.
        Dialogs.create()
            .title("No Selection")
            .masthead("No Person Selected")
            .message("Please select a person in the table.")
            .showWarning();
    }
}

新建和編輯對話方塊

新建和編輯的動作有點工作:我們需要一個自定義帶表單的對話方塊(例如:新的Stage),詢問使用者關於人員的詳情。

設計對話方塊

  1. 在*view*包中建立新的fxml檔案,稱為PersonEditDialog.fxml Create Edit Dialog

  2. 使用GridPanLabelTextFieldButton建立一個對話方塊,如下所示:
    Edit Dialog

建立控制器

為對話方塊建立控制器PersonEditDialogController.java:

PersonEditDialogController.java

package ch.makery.address.view;

import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

import org.controlsfx.dialog.Dialogs;

import ch.makery.address.model.Person;
import ch.makery.address.util.DateUtil;

/**
 * Dialog to edit details of a person.
 * 
 * @author Marco Jakob
 */
public class PersonEditDialogController {

    @FXML
    private TextField firstNameField;
    @FXML
    private TextField lastNameField;
    @FXML
    private TextField streetField;
    @FXML
    private TextField postalCodeField;
    @FXML
    private TextField cityField;
    @FXML
    private TextField birthdayField;


    private Stage dialogStage;
    private Person person;
    private boolean okClicked = false;

    /**
     * Initializes the controller class. This method is automatically called
     * after the fxml file has been loaded.
     */
    @FXML
    private void initialize() {
    }

    /**
     * Sets the stage of this dialog.
     * 
     * @param dialogStage
     */
    public void setDialogStage(Stage dialogStage) {
        this.dialogStage = dialogStage;
    }

    /**
     * Sets the person to be edited in the dialog.
     * 
     * @param person
     */
    public void setPerson(Person person) {
        this.person = person;

        firstNameField.setText(person.getFirstName());
        lastNameField.setText(person.getLastName());
        streetField.setText(person.getStreet());
        postalCodeField.setText(Integer.toString(person.getPostalCode()));
        cityField.setText(person.getCity());
        birthdayField.setText(DateUtil.format(person.getBirthday()));
        birthdayField.setPromptText("dd.mm.yyyy");
    }

    /**
     * Returns true if the user clicked OK, false otherwise.
     * 
     * @return
     */
    public boolean isOkClicked() {
        return okClicked;
    }

    /**
     * Called when the user clicks ok.
     */
    @FXML
    private void handleOk() {
        if (isInputValid()) {
            person.setFirstName(firstNameField.getText());
            person.setLastName(lastNameField.getText());
            person.setStreet(streetField.getText());
            person.setPostalCode(Integer.parseInt(postalCodeField.getText()));
            person.setCity(cityField.getText());
            person.setBirthday(DateUtil.parse(birthdayField.getText()));

            okClicked = true;
            dialogStage.close();
        }
    }

    /**
     * Called when the user clicks cancel.
     */
    @FXML
    private void handleCancel() {
        dialogStage.close();
    }

    /**
     * Validates the user input in the text fields.
     * 
     * @return true if the input is valid
     */
    private boolean isInputValid() {
        String errorMessage = "";

        if (firstNameField.getText() == null || firstNameField.getText().length() == 0) {
            errorMessage += "No valid first name!\n"; 
        }
        if (lastNameField.getText() == null || lastNameField.getText().length() == 0) {
            errorMessage += "No valid last name!\n"; 
        }
        if (streetField.getText() == null || streetField.getText().length() == 0) {
            errorMessage += "No valid street!\n"; 
        }

        if (postalCodeField.getText() == null || postalCodeField.getText().length() == 0) {
            errorMessage += "No valid postal code!\n"; 
        } else {
            // try to parse the postal code into an int.
            try {
                Integer.parseInt(postalCodeField.getText());
            } catch (NumberFormatException e) {
                errorMessage += "No valid postal code (must be an integer)!\n"; 
            }
        }

        if (cityField.getText() == null || cityField.getText().length() == 0) {
            errorMessage += "No valid city!\n"; 
        }

        if (birthdayField.getText() == null || birthdayField.getText().length() == 0) {
            errorMessage += "No valid birthday!\n";
        } else {
            if (!DateUtil.validDate(birthdayField.getText())) {
                errorMessage += "No valid birthday. Use the format dd.mm.yyyy!\n";
            }
        }

        if (errorMessage.length() == 0) {
            return true;
        } else {
            // Show the error message.
            Dialogs.create()
                .title("Invalid Fields")
                .masthead("Please correct invalid fields")
                .message(errorMessage)
                .showError();
            return false;
        }
    }
}

關於該控制器的一些事情應該注意:

  1. setPerson(…)方法可以從其它類中呼叫,用來設定編輯的人員。
  2. 當用戶點選OK按鈕時,呼叫handleOK()方法。首先,通過呼叫isInputValid()方法做一些驗證。只有驗證成功,Person物件使用輸入的資料填充。這些修改將直接應用到Person物件上,傳遞給setPerson(…)
  3. 布林值okClicked被使用,以便呼叫者決定使用者是否點選OK或者Cancel按鈕。

連線檢視和控制器

使用已經建立的檢視(FXML)和控制器,需要連線到一起。

  1. 使用SceneBuilder開啟PersonEditDialog.fxml檔案
  2. 在左邊的*Controller*組中選擇PersonEditDialogController作為控制器類
  3. 設定所有TextFieldfx:id到相應的控制器欄位上。
  4. 設定兩個按鈕的onAction到相應的處理方法上。

開啟對話方塊

MainApp中新增一個方法載入和顯示編輯人員的對話方塊。

MainApp.java

/**
 * Opens a dialog to edit details for the specified person. If the user
 * clicks OK, the changes are saved into the provided person object and true
 * is returned.
 * 
 * @param person the person object to be edited
 * @return true if the user clicked OK, false otherwise.
 */
public boolean showPersonEditDialog(Person person) {
    try {
        // Load the fxml file and create a new stage for the popup dialog.
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(MainApp.class.getResource("view/PersonEditDialog.fxml"));
        AnchorPane page = (AnchorPane) loader.load();

        // Create the dialog Stage.
        Stage dialogStage = new Stage();
        dialogStage.setTitle("Edit Person");
        dialogStage.initModality(Modality.WINDOW_MODAL);
        dialogStage.initOwner(primaryStage);
        Scene scene = new Scene(page);
        dialogStage.setScene(scene);

        // Set the person into the controller.
        PersonEditDialogController controller = loader.getController();
        controller.setDialogStage(dialogStage);
        controller.setPerson(person);

        // Show the dialog and wait until the user closes it
        dialogStage.showAndWait();

        return controller.isOkClicked();
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    }
}

新增下面的方法到PersonOverviewController中。當用戶按下*New*或*Edit*按鈕時,這些方法將從MainApp中呼叫showPersonEditDialog(...)

PersonOverviewController.java

/**
 * Called when the user clicks the new button. Opens a dialog to edit
 * details for a new person.
 */
@FXML
private void handleNewPerson() {
    Person tempPerson = new Person();
    boolean okClicked = mainApp.showPersonEditDialog(tempPerson);
    if (okClicked) {
        mainApp.getPersonData().add(tempPerson);
    }
}

/**
 * Called when the user clicks the edit button. Opens a dialog to edit
 * details for the selected person.
 */
@FXML
private void handleEditPerson() {
    Person selectedPerson = personTable.getSelectionModel().getSelectedItem();
    if (selectedPerson != null) {
        boolean okClicked = mainApp.showPersonEditDialog(selectedPerson);
        if (okClicked) {
            showPersonDetails(selectedPerson);
        }

    } else {
        // Nothing selected.
        Dialogs.create()
            .title("No Selection")
            .masthead("No Person Selected")
            .message("Please select a person in the table.")
            .showWarning();
    }
}

在Scene Builder中開啟PersonOverview.fxml檔案,為New和Edit按鈕的*On Action*中選擇對應的方法。

完成!

現在你應該有一個可以工作的*Address應用*。應用能夠新增、編輯和刪除人員。這裡甚至有一些文字欄位的驗證避免壞的使用者輸入。

我希望本應用的概念和結構讓開始編寫自己的JavaFX應用!玩的開心。

歡迎關注公眾號:

相關推薦

JavaFx教程部分使用者的互動

第3部分的主題: 在表中反應選擇的改變(TableView中)。 增加增加,編輯和刪除按鈕的功能。 建立自定義彈出對話方塊編輯人員。 驗證使用者輸入。 響應表的選擇 顯然,我們還沒有使用應用程式的右邊。想法是當用戶選擇表中的人員時,在右邊顯示人員的詳情。 首先

JavaFx教程部分CSS 樣式

第4部分主題 CSS樣式表 新增應用程式圖示 CSS樣式表 在JavaFX中,你能使用層疊樣式表修飾你的使用者介面。這非常好!自定義Java應用介面從來不是件簡單的事情。 在本教程中,我們將建立一個*DarkTheme*主題,靈感來自於Windows 8 Metr

JavaFx教程部分將資料用 XML 格式儲存

第5部分的主題 持久化資料為XML 使用JavaFX的FileChooser 使用JavaFX的選單 在使用者設定中儲存最後開啟的檔案路徑。 現在我們的地址應用程式的資料只儲存在記憶體中。每次我們關閉應用程式,資料將丟失,因此是時候開始考慮持久化儲存資料了。 儲

JavaFx教程部分統計圖

第6部分的主題 建立一個統計圖顯示生日的分佈。 生日統計 在AddressApp中所有人員都有生日。當我們人員慶祝他們生日的時候,如果有一些生日的統計不是會更好。 我們使用柱狀圖,包含每個月的一個條形。每個條形顯示在指定月份中有多少人需要過生日。 統計FXML檢視

JavaFx教程部分部署

我想已經寫到本教程系列的最後一部分了,應該教你如何部署(例如:打包和釋出)AddressApp 第7部分的主題 使用e(fx)clipse本地包(Native Package)部署我們的JavaFX應用程式。 什麼是部署 部署是打包和釋出軟體給使用者的過程。這是軟體

遊資課程大局觀——炒股養家

1:炒股養家悟道2:炒股養家的馬甲3:炒股養家的大局觀4:炒股養家做熱點挖掘5:炒股養家做訊息溢價1:炒股養家的悟道炒股養家90年代開始入市炒股,據說投入股市的資金只有1萬,到2009年的時候,已經累積到50萬,通過研究網際網路上各類炒股大神的操盤記錄,

設計模式四篇建造模式也沒那麼難

![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3c68df3e1ac44ba68c613b630fa5c43f~tplv-k3u1fbpfcp-zoom-1.image) # 一 引言 說明:如果想要直接閱讀定義等理論內容,可以直接跳轉到第二大點 在生活

小白看的Java教程十七章,Mr.R和Mr.WJava中的IO

File類(掌握) File課理解為檔案和資料夾(目錄),用於表示磁碟中某個檔案或資料夾的路徑。該類包含了檔案的建立、刪除、重新命名、判斷是否存在等方法。 只能設定和獲取檔案本身的資訊(檔案大小,是否可讀),不能設定和獲取檔案裡面的內容。 Unix: 嚴

JavaFx教程第一部分Scene Builder

第一部分的主題 開始瞭解 JavaFX 。 建立並執行一個 JavaFX 專案。 使用 Scene Builder 來設計使用者介面。 使用 模型 - 檢視 - 控制器(MVC)模式 構造基礎的應用。 你需要準備 最新的 Java JDK 8 (包含 JavaF

Kaggle word2vec NLP 教程 部分詞向量的更多樂趣

第三部分:詞向量的更多樂趣 程式碼 第三部分的程式碼在這裡。 單詞的數值表示 現在我們有了訓練好的模型,對單詞有一些語義理解,我們應該如何使用它? 如果你看它的背後,第 2 部分訓練的 Word2Vec 模型由詞彙表中每個單詞的特徵向量組成,儲存在一個名為sy

機器學習系列文章1部分為什麼機器學習很重要 ?

目錄 路線圖 關於作者 簡單,簡單的解釋,附有數學,程式碼和現實世界的例子。 這個系列是一本完整的電子書!在這裡下載。免費下載,貢獻讚賞(paypal.me/ml4h) 路線圖 第1部分:為什麼機器學習很重要。人工智慧和機器學習的大

機器學習系列文章6部分最好的機器學習資源

目錄 基金會 程式設計 線性代數 概率與統計 微積分 機器學習 培訓 教科書 深度學習 培訓 專案 閱讀 強化學習 培訓 專案 閱讀 人工智慧 簡訊 別人的建議 製作人工智慧,機器學習和深度學習課程的資源彙編。 關於制定課

機器學習系列文章5部分強化學習

目錄 你做到了! 結束思考 探索和開發。馬爾可夫決策過程。Q-learning,政策學習和深度強化學習。 “我只吃了一些巧克力來完成最後一節。” 在有監督的學習中,訓練資料帶有來自某些神聖的“主管”的答案。如果只有這樣的生活! 在強化學

計算機組成設計硬體/軟體介面計算機的算術運算

【計算機組成與設計:硬體/軟體介面】第三章:計算機的算術運算 標籤(空格分隔):【計算機組成與設計:硬體/軟體介面】 第三章:計算機的算術運算 第三章:計算機的算術運算 3.1 引言 3.2 加法和

SpringCloud Greenwich版本服務消費者(Feign)

一、SpringCloud版本 本文介紹的Springboot版本為2.1.1.RELEASE,SpringCloud版本為Greenwich.RC1,JDK版本為1.8,整合環境為IntelliJ IDEA 二、Feign介紹 Feign是一個宣告式的Web服務客戶端。這使得W

雲週刊199期週年專輯-阿里畢玄程式設計師的成長路線

本期頭條 三週年專輯-阿里畢玄:程式設計師的成長路線 2018年12月20日,雲棲社群3週歲生日。阿里巴巴常說“晴天修屋頂”,所以我們特別策劃了這個專輯——分享給開發者們20個阿里故事,50本書籍。第一位是林昊(畢玄)。在這篇《程式設計師的成長路線》裡,阿里基礎設施負責人畢玄結合自己的經歷跟大家講述了他在

Effective java 學習對於所有物件都通用的方法

第八條:覆蓋equals是請遵守通用約定 滿足下列四個條件之一,就不需要覆蓋equals方法: 類的每個例項本質上都已唯一的。不包括代表值的類,如:Integer,String等,Object提供的equals方法就夠用了 不關心是否提供了“邏輯相等”的測試功能。對

吳恩達機器學習筆記線性迴歸回顧

本章是對線性代數的一些簡單回顧,由於之前學過,所以這裡只是簡單的將課程中的一些例子粘過來 矩陣表示 矩陣加法和標量乘法 矩陣向量乘法 用矩陣向量乘法來同時計算多個預測值 矩陣乘法 用矩陣乘法同時計算多個迴歸

如何校驗郵件地址的有效性原理二

目標郵件伺服器 此過程的第一步是檢查是否存在我們要驗證的電子郵件地址域的MX記錄。MX記錄是儲存有關處理特定域的郵件的郵件伺服器資訊的DNS記錄。如果域名沒有MX記錄,則也不存在有效的電子郵件地址。我們來看看使用nslookup 命令的兩個例子 。請注意,您可以類似地使用其

滲透課程篇-體驗http協議的應用

load 簡單介紹 class 發送數據 數據交互 動手實驗 服務端 yun 屬於 之前我們都了解了,訪問網頁時,客戶端與服務端之間的請求與響應數據交互。本篇就淺談它的應用。 利用HTTP攔截突破前段驗證 比方說,我們在某個網頁提交某些數據(例如留言、上傳、插入xss等)