Tutorial JavaFX (Português)

Parte 2: Modelo e TableView

Screenshot AddressApp Part 2

Tópicos na Parte 2

  • Criar a Classe Model
  • Usando a classe model em uma ObservableList
  • Mostrar dados na TableView usando Controllers

Criar a Classe Model

Nós precisamos de uma classe model para guardar informação sobre as pessoas na nossa agenda. Adicione uma nova classe ao pacote model (ch.makery.address.model) chamado Person (pessoa). A classe Person (pessoa) terá um pouco de variáveis de instância para o nome, endereço e aniversário. Adicione o código seguinte à classe. Eu explicarei algumas coisas específicas do JAvaFX depois do código.

Person.java
package ch.makery.address.model;

import java.time.LocalDate;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

/**
 * Classe Model para uma Person (pessoa).
 *
 * @author Marco Jakob
 */
public class Person {

	private final StringProperty firstName;
	private final StringProperty lastName;
	private final StringProperty street;
	private final IntegerProperty postalCode;
	private final StringProperty city;
	private final ObjectProperty<LocalDate> birthday;

	/**
	 *  Construtor padrão.
	 */
	public Person() {
		this(null, null);
	}
	
	/**
	 * Construtor com alguns dados iniciais.
	 * 
	 * @param firstName Primeiro nome da Pessoa.
	 * @param lastName Sobrenome da Pessoa.
	 */
	public Person(String firstName, String lastName) {
		this.firstName = new SimpleStringProperty(firstName);
		this.lastName = new SimpleStringProperty(lastName);
		
		// Alguns dados de exemplo, apenas para testes.
		this.street = new SimpleStringProperty("some street");
		this.postalCode = new SimpleIntegerProperty(1234);
		this.city = new SimpleStringProperty("some city");
		this.birthday = new SimpleObjectProperty<LocalDate>(LocalDate.of(1999, 2, 21));
	}
	
	public String getFirstName() {
		return firstName.get();
	}

	public void setFirstName(String firstName) {
		this.firstName.set(firstName);
	}
	
	public StringProperty firstNameProperty() {
		return firstName;
	}

	public String getLastName() {
		return lastName.get();
	}

	public void setLastName(String lastName) {
		this.lastName.set(lastName);
	}
	
	public StringProperty lastNameProperty() {
		return lastName;
	}

	public String getStreet() {
		return street.get();
	}

	public void setStreet(String street) {
		this.street.set(street);
	}
	
	public StringProperty streetProperty() {
		return street;
	}

	public int getPostalCode() {
		return postalCode.get();
	}

	public void setPostalCode(int postalCode) {
		this.postalCode.set(postalCode);
	}
	
	public IntegerProperty postalCodeProperty() {
		return postalCode;
	}

	public String getCity() {
		return city.get();
	}

	public void setCity(String city) {
		this.city.set(city);
	}
	
	public StringProperty cityProperty() {
		return city;
	}

	public LocalDate getBirthday() {
		return birthday.get();
	}

	public void setBirthday(LocalDate birthday) {
		this.birthday.set(birthday);
	}
	
	public ObjectProperty<LocalDate> birthdayProperty() {
		return birthday;
	}
}

Explicações


Uma Lista de Pessoas

Os principais dados que nossa aplicação gerencia é um monte de pessoas. Vamos criar uma lista para objetos Person dentro da classe MainApp. Todos as outras classes controller classes obterão acesso posteriormente à lista central dentro de MainApp.

ObservableList

Nós estamos trabalhando com classes view JavaFX que precisam de ser informadas sobre quaisquer mudanças feitas à lista de pessoas. Isto é importante, caso contrário a view não seria sincronizada com os dados. Para este propósito, JavaFX introduz algumas novas classes de Coleção.

Para aquelas coleções, nós precisamos da ObservableList. Para criar uma nova ObservableList, adicione o código seguinte ao começo da classe MainApp. Nós adicionaremos também um construtor que cria alguns dados de exemplo e um método getter público:

MainApp.java

    // ... APÓS AS OUTRAS VARIÁVEIS ...

	/**
	 * Os dados como uma observable list de Persons.
	 */
	private ObservableList<Person> personData = FXCollections.observableArrayList();

	/**
	 * Construtor
	 */
	public MainApp() {
		// Add some sample data
		personData.add(new Person("Hans", "Muster"));
		personData.add(new Person("Ruth", "Mueller"));
		personData.add(new Person("Heinz", "Kurz"));
		personData.add(new Person("Cornelia", "Meier"));
		personData.add(new Person("Werner", "Meyer"));
		personData.add(new Person("Lydia", "Kunz"));
		personData.add(new Person("Anna", "Best"));
		personData.add(new Person("Stefan", "Meier"));
		personData.add(new Person("Martin", "Mueller"));
	}
  
	/**
	 * Retorna os dados como uma observable list de Persons. 
	 * @return
	 */
	public ObservableList<Person> getPersonData() {
		return personData;
	}
  
    // ... O RESTANTE DA CLASSE ...

O PersonOverviewController

Agora vamos finalmente colocar alguns dados em nossa tabela. Nós precisaremos de um controller para nosso PersonOverview.fxml.

  1. Criar uma classe normal dentro do pacote view chamada PersonOverviewController.java. (Nós devemos colocá-la no mesmo pacote do que a PersonOverview.fxml, entretanto o SceneBuilder não o encontrará pelo menos não na versão atual).
  2. Nós adicionaremos algumas variáveis de instância que nos dá acesso à tabela e às labels dentro da view. Os campos e alguns métodos tem uma anotação especial @FXML. Isso é enecessário para o arquivo FXML ter acesso aos campos e métodos privados. Após nós termos tudo ajustado no arquivo FXML, a aplicação vai preencher automaticamente as variáveis quando o arquivo FXML é carregado. Então vamos adicionar o código abaixo:
Nota: Lembre-se de sempre usar os imports javafx, NÃO USE awt or swing!
PersonOverviewController.java
package ch.makery.address.view;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import ch.makery.address.MainApp;
import ch.makery.address.model.Person;

public class PersonOverviewController {
    @FXML
    private TableView<Person> personTable;
    @FXML
    private TableColumn<Person, String> firstNameColumn;
    @FXML
    private TableColumn<Person, String> lastNameColumn;

    @FXML
    private Label firstNameLabel;
    @FXML
    private Label lastNameLabel;
    @FXML
    private Label streetLabel;
    @FXML
    private Label postalCodeLabel;
    @FXML
    private Label cityLabel;
    @FXML
    private Label birthdayLabel;

    // Reference to the main application.
    private MainApp mainApp;

    /**
     * O construtor.
     * O construtor é chamado antes do método inicialize().
     */
    public PersonOverviewController() {
    }

    /**
     * Inicializa a classe controller. Este método é chamado automaticamente
     *  após o arquivo fxml ter sido carregado.
     */
    @FXML
    private void initialize() {
    	// Inicializa a tablea de pessoa com duas colunas.
        firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
        lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
    }

    /**
     * É chamado pela aplicação principal para dar uma referência de volta a si mesmo.
     * 
     * @param mainApp
     */
    public void setMainApp(MainApp mainApp) {
        this.mainApp = mainApp;

        // Adiciona os dados da observable list na tabela
        personTable.setItems(mainApp.getPersonData());
    }
}

Agora este código provavelmente vai precisar de explicação.:

  • Todos os campos e métodos onde o arquivo fxml precisa de acesso devem ser anotados com @FXML. Na verdade, omente se eles forem private, mas é melhor tê-los private e marcá-los com a anotação!
  • O método initialize() é chamado automaticamente após o arquivo fxml ter sido carregado. Nessa hora, todos os campos FXML já devem ter sido inicializados.
  • O método setCellValueFactory(...) que nós definimos nas colunas da tabela são usados para determinar qual campo dentro dos objetos de Person devem ser usados para determinda coluna. A seta -> indica que nós estamos usando um recurso do Java 8 chamado Lambdas. (Outra opção seria usar uma PropertyValueFactory, mas esta não é type-safe (segura por tipo)).

Conectando a MainApp com o PersonOverviewController

O método setMainApp(...) deve ser chamado pela classe MainApp. Isso nos dá uma maneira de acessar o objeto MainApp e obter a lista de Persons e outras coisas. Substitua o método showPersonOverview() pelo abaixo. Ele contém duas linhas adicionais:

MainApp.java - novo método showPersonOverview()
/**
 * Mostra a person overview dentro do root layout.
 */
public void showPersonOverview() {
    try {
        // Carrega a person overview.
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
        AnchorPane personOverview = (AnchorPane) loader.load();

        // Define a person overview no centro do root layout.
        rootLayout.setCenter(personOverview);

        // Dá ao controlador acesso à the main app.
        PersonOverviewController controller = loader.getController();
        controller.setMainApp(this);

    } catch (IOException e) {
        e.printStackTrace();
    }
}

Ligar a View ao Controller

Nós estamos quase lá. Mas uma pequena coisa está faltando: Nós não contamos ao nosso arquivo PersonOverview.fxml ainda, qual controller usar e qual elemento deve combinar com qual campo dentro do controller.

  1. Abra PersonOverview.fxml com o SceneBuilder.

  2. Abra o grupo Controller no lado direito e selecione PersonOverviewController como controller class.
    Set Controller Class

  3. Selecione a TableView no grupo Hierarchy e escolha no grupo Code o campo personTable como fx:id.
    Set TableView fx:id

  4. Faça o mesmo para as colunas, selecione firstNameColumn e lastNameColumn como fx:id respectivamente.

  5. Para cada label nas segunda coluna, escolha o fx:id correspondente.
    Set Label fx:id

  6. Importante: Volta ao Eclipse e atualize (refresh) o projeto AddressApp inteiro project (F5). Isso é necessário porque o Eclipse às vezes não sabe sobre mudanças que foram feitas dentro do Scene Builder.


Inicie a Aplicação

Quando você iniciar a sua aplicação agora, você deve ver algo como o screenshot no começo deste post do blog.

Parabéns!

O Que Vem Depois?

No Tutorial Parte 3 nós adicionaremos mais funcionalidades como adicionar, deletar e editar Persons.

Alguns outros artigos que você deve achar interessante (em inglês)