
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_20ou 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.fxmldentro do pacote view.

-
Use um
GridPane,Labels,TextFields eButtons 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
PersonEditDialogControllercomo classe controller. - Defina o fx:id de todos os
TextFields 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.