Tópicos na Parte 3
- Reagir às mudanças de seleção na tabela.
- Adicionar funcionalidade aos botões de add (adicionar), edit (editar), e remove (remover).
- Criar uma janela popup customizada para editar uma pessoa.
- Validar entrada do usuário.
Reagir às Seleções de Tabela
Obviamente, nós ainda não usamos o lado direito da nossa aplicação. A idéia é mostrar os detalhes sobre uma pessoa no lado direito quando o usuário selecionar uma pessoa na tabela.
Primeiro, vamos adicionar um novo método dentro de PersonOverviewController
que nos ajuda a preencher as labels com os dados de uma única Person
.
Crie um método chamado showPersonDetails(Person person)
. Vá por todas as labels e defina o texto usando setText(...)
com detalhes da pessoa. Se null
é passado como parâmetro, todas as labels devem ser limpas.
PersonOverviewController.java
/** * PReenche todos os campos de texto para mostrar detalhes sobre a pessoa. * Se a pessoa especificada for null, todos os campos de texto são limpos. * * @param person a pessoa ou null */ private void showPersonDetails(Person person) { if (person != null) { // Preenche as labels com informações do objeto person. firstNameLabel.setText(person.getFirstName()); lastNameLabel.setText(person.getLastName()); streetLabel.setText(person.getStreet()); postalCodeLabel.setText(Integer.toString(person.getPostalCode())); cityLabel.setText(person.getCity()); // TODO: Nós precisamos de uma maneira de converter o aniversário em um String! // birthdayLabel.setText(...); } else { // Person é null, remove todo o texto. firstNameLabel.setText(""); lastNameLabel.setText(""); streetLabel.setText(""); postalCodeLabel.setText(""); cityLabel.setText(""); birthdayLabel.setText(""); } }
Converter a Data de Aniversário em um String
Você vai perceber que nós não poderíamos definir o birthday
em uma Label
porque ele é do tipo LocalDate
e não uma String
. Nós devemos formatar a data primeiro.
Nós usaremos a conversão de LocalDate
para String
e vice versa em vários lugares. è uma boa prática criar uma classe helper (auxiliar) com métodosstatic
para isso. Nós chamaremos ela de DateUtil
e colocá-la em um pacote separado chamado 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; /** * Funções auxiliares para lidar com datas. * * @author Marco Jakob */ public class DateUtil { /** O padrão usado para conversão. Mude como quiser. */ private static final String DATE_PATTERN = "dd.MM.yyyy"; /** O formatador de data. */ private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_PATTERN); /** * Retorna os dados como String formatado. O * {@link DateUtil#DATE_PATTERN} (padrão de data) que é utilizado. * * @param date A data a ser retornada como String * @return String formadado */ public static String format(LocalDate date) { if (date == null) { return null; } return DATE_FORMATTER.format(date); } /** * Converte um String no formato definido {@link DateUtil#DATE_PATTERN} * para um objeto {@link LocalDate}. * * Retorna null se o String não puder se convertido. * * @param dateString a data como String * @return o objeto data ou null se não puder ser convertido */ public static LocalDate parse(String dateString) { try { return DATE_FORMATTER.parse(dateString, LocalDate::from); } catch (DateTimeParseException e) { return null; } } /** * Checa se o String é uma data válida. * * @param dateString A data como String * @return true se o String é uma data válida */ public static boolean validDate(String dateString) { // Tenta converter o String. return DateUtil.parse(dateString) != null; } }
DATE_PATTERN
. Para todos os formatos possíveis veja (em inglês) DateTimeFormatter.
Usar o DateUtil
Agora nós precisamos usar nosso novo DateUtil
no método showPersonDetails
da classe PersonOverviewController
. Substitua o TODO que nós colocamos pela linha seguinte:
birthdayLabel.setText(DateUtil.format(person.getBirthday()));
Detectar Mundanças na Seleção da Tabela
Para ser informado quando o usuário selecionar uma pessoa na tabela de pessoas, nós devemos detectar (listen) mudanças.
Existe uma interface no JavaFX chamada ChangeListener
com um método chamado changed(...)
. O método tem três parâmetros: observable
, oldValue
, e newValue
.
Nós vamos criar um ChangeListener
usando uma expressão lambda do Java 8. Vamos adicionar algumas linhas ao método initialize()
na classe PersonOverviewController
. Agora ela está assim:
PersonOverviewController.java
@FXML private void initialize() { // Inicializa a tabela de pessoas com duas colunas. firstNameColumn.setCellValueFactory( cellData -> cellData.getValue().firstNameProperty()); lastNameColumn.setCellValueFactory( cellData -> cellData.getValue().lastNameProperty()); // Limpa os detalhes da pessoa. showPersonDetails(null); // Detecta mudanças de seleção e mostra os detalhes da pessoa quando houver mudança. personTable.getSelectionModel().selectedItemProperty().addListener( (observable, oldValue, newValue) -> showPersonDetails(newValue)); }
Com showPersonDetails(null);
nós resetamos os detalhes da pessoa.
Com personTable.getSelectionModel...
nós obtemos a selectedItemProperty da tabela de pessoas e adiciona um listener (detector) a ela. Sempre que o usuário selecionar uma pessoa na tabela, nossa expressão lambda é executada. Nós obtemos a pessoa selecionada recentemente e passamos para o método showPersonDetails(...)
.
Tente rodar sua aplicação neste ponto. Verifique que quando você seleciona uma pessoa na tabela, detalhes saquela pessoa são mostrados à direita.
Se algo não funcionar, você pode comprar sua classe PersonOverviewController
com PersonOverviewController.java.
O Botão Deletar
Nossa interface de usuário já contém um botão de delete, mas sem nenhuma funcionalidade. Nós podemos selecionar a ação para um botão dentro do Scene Builder. Qualquer método dentro do nosso que for anotado com @FXML
(ou for public) é acessível pelo Scene Builder. Assim, vamos primeiro adicionar um método delete ao fim de nossa classe PersonOverviewController
:
PersonOverviewController.java
/** * Chamado quando o usuário clica no botão delete. */ @FXML private void handleDeletePerson() { int selectedIndex = personTable.getSelectionModel().getSelectedIndex(); personTable.getItems().remove(selectedIndex); }
Agora, abra o arquivo PersonOverview.fxml
no SceneBuilder. Selecione o botão Delete, abra o grupo Code e escolha handleDeletePerson
no dropdown de On Action.
Lidando com Erros
Se você rodar a aplicação neste ponto, você poderá deletar a pessoa selecionada da tabela. Mas o que acontece se você clicar o botão delete enquanto nenhuma pessoa estiver selecionada na tabela?
Haverá uma ArrayIndexOutOfBoundsException
porque ele não poderia remover uma pessoa no index (na posição) -1
. O index (a posição) -1
foi retornado pelo método getSelectedIndex()
- que significa que há nenhuma seleção.
Ignorar tal erro não é muito legal, é claro. Nós deveríamos deixar o usuário saber que ele/ela deve selecionar uma pessoa antes de deletar. (Melhor seria se nós desabilitássemos o botão, então o usuário não teria chance de fazer algo errado.)
Nós adicionaremos uma janela de popup para informar o usuário. Você precisará adicionar uma biblioteca para o Dialogs:
- Baixe este controlsfx-8.0.6_20.jar (você poderia também obtê-lo do siteControlsFX Website).
Importante: O ControlsFX deve estar na versão8.0.6_20
ou maior para trabalhar com oJDK 8u20
, versões anteriores vão ter problemas de compatibilidade. - Crie uma subpasta lib no projeto e adicione o arquivo controlsfx-jar a esta pasta.
- Adcione a biblioteca ao classpath do seu projeto: No Eclipse clique com o botão direito no arquivo | Build Path | Add to Build Path. Agora o Eclipse sabe sobre a biblioteca.
Com algumas mudanças feitas no método handleDeletePerson()
, nós podemos mostrar uma janela simples de popup quando o usuário clicar no botão delete quando não houver uma pessoa selecionada na tabela:
PersonOverviewController.java
/** * Chamado quando o usuário clica no botão delete. */ @FXML private void handleDeletePerson() { int selectedIndex = personTable.getSelectionModel().getSelectedIndex(); if (selectedIndex >= 0) { personTable.getItems().remove(selectedIndex); } else { // Nada selecionado. Alert alert = new Alert(AlertType.WARNING); alert.setTitle("Nenhuma seleção"); alert.setHeaderText("Nenhuma Pessoa Selecionada"); alert.setContentText("Por favor, selecione uma pessoa na tabela."); alert.showAndWait(); } }
Os Dialogs New (Novo) e Edit (Editar)
As ações new (novo) e edit (editar) são um pouco mais trabalhosas: Nós precisaremos de uma dialog customizada com um formulário para perguntar o usuário os detalhes da pessoa.
Desenhando o Dialog
Crie um novo arquivo fxml chamado
PersonEditDialog.fxml
dentro do pacote view.
Use um
GridPane
,Label
s,TextField
s eButton
s para criar um Dialog como o seguinte:
Se você não quiser fazer o trabalho, você pode baixar este PersonEditDialog.fxml.
Criar o Controller
Crie o controller para o Dialog como PersonEditDialogController.java
:
PersonEditDialogController.java
package ch.makery.address.view; import javafx.fxml.FXML; import javafx.scene.control.TextField; import javafx.stage.Stage; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import ch.makery.address.model.Person; import ch.makery.address.util.DateUtil; /** * Dialog para editar detalhes de uma pessoa. * * @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; /** * Inicializa a classe controlle. Este método é chamado automaticamente * após o arquivo fxml ter sido carregado. */ @FXML private void initialize() { } /** * Define o palco deste dialog. * * @param dialogStage */ public void setDialogStage(Stage dialogStage) { this.dialogStage = dialogStage; } /** * Define a pessoa a ser editada no 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"); } /** * Retorna true se o usuário clicar OK,caso contrário false. * * @return */ public boolean isOkClicked() { return okClicked; } /** * Chamado quando o usuário clica 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(); } } /** * Chamado quando o usuário clica Cancel. */ @FXML private void handleCancel() { dialogStage.close(); } /** * Valida a entrada do usuário nos campos de texto. * * @return true se a entrada é válida */ private boolean isInputValid() { String errorMessage = ""; if (firstNameField.getText() == null || firstNameField.getText().length() == 0) { errorMessage += "Nome inválido!\n"; } if (lastNameField.getText() == null || lastNameField.getText().length() == 0) { errorMessage += "Sobrenome inválido!\n"; } if (streetField.getText() == null || streetField.getText().length() == 0) { errorMessage += "Rua inválida!\n"; } if (postalCodeField.getText() == null || postalCodeField.getText().length() == 0) { errorMessage += "Código Postal inválido!\n"; } else { // tenta converter o código postal em um int. try { Integer.parseInt(postalCodeField.getText()); } catch (NumberFormatException e) { errorMessage += "Código Postal inválido (deve ser um inteiro)!\n"; } } if (cityField.getText() == null || cityField.getText().length() == 0) { errorMessage += "Cidade inválida!\n"; } if (birthdayField.getText() == null || birthdayField.getText().length() == 0) { errorMessage += "Aniversário inválido!\n"; } else { if (!DateUtil.validDate(birthdayField.getText())) { errorMessage += "Aniversário inválido. Use o formato dd.mm.yyyy!\n"; } } if (errorMessage.length() == 0) { return true; } else { // Mostra a mensagem de erro. Alert alert = new Alert(AlertType.ERROR); alert.setTitle("Campos Inválidos"); alert.setHeaderText("Por favor, corrija os campos inválidos"); alert.setContentText(errorMessage); alert.showAndWait(); return false; } } }
Algumas coisas para notar sobre este controller:
- O método
setPerson(...)
pode ser chamado por outra classe para definir a pessoa a ser editada. - Quando o usuário clica o botão OK, o método
handleOk()
é chamado. Primeiro, alguma validação é feita pela chamada do métodoisInputValid()
. Só se a validação tiver sucesso, o objeto pessoa é preenchido com os dados que o usuário inseriu. Aquelas mudanças serão aplicadas diretamente ao objeto da pessoa que foi passado para o métodosetPerson(...)
! - O booleano
okClicked
é usado então o método chamador pode determinar se o usuário clicou no botão OK ou Cancel.
Ligar View e Controller
Com a View (FXML) e o controller criado nós precisamos ligá-los:
- Abra o
PersonEditDialog.fxml
. - No grupo Controller no lado esquerdo selecione o
PersonEditDialogController
como classe controller. - Defina o fx:id de todos os
TextField
s para o campo correspondente do controller. - Defina o onAction dos dois botões ao método handler correspondente.
Abrindo o Dialog
Adicione um método para carregar e mostrar o EditPersonDialog
dentro do nosso MainApp
:
MainApp.java
/** * Abre uma janela para editar detalhes para a pessoa especificada. Se o usuário clicar * OK, as mudanças são salvasno objeto pessoa fornecido e retorna true. * * @param person O objeto pessoa a ser editado * @return true Se o usuário clicou OK, caso contrário false. */ public boolean showPersonEditDialog(Person person) { try { // Carrega o arquivo fxml e cria um novo stage para a janela popup. FXMLLoader loader = new FXMLLoader(); loader.setLocation(MainApp.class.getResource("view/PersonEditDialog.fxml")); AnchorPane page = (AnchorPane) loader.load(); // Cria o palco dialogStage. Stage dialogStage = new Stage(); dialogStage.setTitle("Edit Person"); dialogStage.initModality(Modality.WINDOW_MODAL); dialogStage.initOwner(primaryStage); Scene scene = new Scene(page); dialogStage.setScene(scene); // Define a pessoa no controller. PersonEditDialogController controller = loader.getController(); controller.setDialogStage(dialogStage); controller.setPerson(person); // Mostra a janela e espera até o usuário fechar. dialogStage.showAndWait(); return controller.isOkClicked(); } catch (IOException e) { e.printStackTrace(); return false; } }
Adicione os seguitnes métodos ao PersonOverviewController
. Esses métodos chamarão o showPersonEditDialog(...)
do MainApp
quando o usuário clicar os botões new ou edit.
PersonOverviewController.java
/** * Chamado quando o usuário clica no botão novo. Abre uma janela para editar * detalhes da nova pessoa. */ @FXML private void handleNewPerson() { Person tempPerson = new Person(); boolean okClicked = mainApp.showPersonEditDialog(tempPerson); if (okClicked) { mainApp.getPersonData().add(tempPerson); } } /** * Chamado quando o usuário clica no botão edit. Abre a janela para editar * detalhes da pessoa selecionada. */ @FXML private void handleEditPerson() { Person selectedPerson = personTable.getSelectionModel().getSelectedItem(); if (selectedPerson != null) { boolean okClicked = mainApp.showPersonEditDialog(selectedPerson); if (okClicked) { showPersonDetails(selectedPerson); } } else { // Nada seleciondo. Alert alert = new Alert(AlertType.WARNING); alert.setTitle("Nenhuma seleção"); alert.setHeaderText("Nenhuma Pessoa Selecionada"); alert.setContentText("Por favor, selecione uma pessoa na tabela."); alert.showAndWait(); } }
Abra o arquivo PersonOverview.fxml
no Scene Builder. Escolha os métodos correspondentes em On Action para os botões new e edit.
Pronto!
Você deve ter uma Aplicação de Endereços (Agenda) agora. A aplicação pode adicionar, editar e deletar pessoas. Há também validação para os campos de texto para evitar más entradas do usuário.
Eu espero que os conceitos e estrutura desta aplicação vão levá-los a começar a escrever suas próprias aplicações JavaFX! Divirtam-se.
O Que Vem Depois?
No Tutorial Parte 4 nós adicionaremos alguma estilização CSS.