Tag Archive: netbeans



Parte 1Parte 2Parte 3Parte 4Parte 5

Olá! Nesta segunda parte do tutorial sobre criação de relatórios, vamos fazer nossos primeiros exemplos, entretanto, para que possamos ver algum resultado, ou seja, nossos relatórios preenchidos com valores, precisamos de uma base de dados para extrairmos tais dados. Ao invés de criar uma base de dados na mão, iremos utilizar uma base já pronta. Nos nossos exemplos, vamos utilizar o MySQL como SGBD e iremos utilizar também o “Sakila Sample Database” para extrairmos nossos dados. Essa base de dados, é uma base de exemplo que é disponibilizada no site no MySQL (http://dev.mysql.com/doc/sakila/en/sakila.html), que pode ser usada por desenvolvedores para fazer testes com consultas SQL. Como nossos primeiros relatórios vão trabalhar exclusivamente com SQL, vamos utilizar essa base de dados para facilitar nossa vida.

Então, mãos a obra! Primeiramente, acesse http://dev.mysql.com/doc/index-other.html e procure por “Sample Databases“. Nesta seção, são apresentadas algumas bases de dados de exemplo que podemos baixar. Procure pela sakila database e baixe a base de dados escolhendo o formato desejado (TGZ ou Zip) na primeira coluna. Para facilitar, este link é o link para baixar a versão em Zip.

Quando terminar de baixar o arquivo, descompacte-o. Dentro dele estão contidos três arquivos:

  1. sakila-schema.sql: é a estrutura da base de dados;
  2. sakila-data.sql: são os dados da base de dados;
  3. sakila.mwb: é um projeto do MySQL workbench (http://wb.mysql.com/).

Vamos então carregar os dois primeiros arquivos para o nosso SGBD. Para isso, abra o MySQL Command Line Client, digite a senha do root e tecle <ENTER> para fazer o login. Se estiver usando Linux, ou estiver com o bin do MySQL configurado no PATH do Windows, faça o login no MySQL usando o comando “mysql -u root -p” (sem as aspas). Quando estamos logados no interpretador do MySQL, podemos utilizar alguns comandos para a administração das bases de dados existentes. Um desses comandos é o “source” que é utilizado para carregar e executar comandos do SGBD que estão contidos em um arquivo de texto. Vamos utilizar então este comando para carregar os dois primerios arquivos do sakila database (a estrutura e os dados). Copiei os arquivos sakila-schema.sql e sakila-data.sql para o C: para facilitar a digitação do comando. Veja na Figura abaixo o comando sendo usado para a carga do primeiro arquivo (sakila-schema.sql).

 

Figura 1

Usando o comando source para carregar o arquivo sakila-schema.sql

 

Assim que você digitar o comando “source C:/sakila-schema.sql;” (sem as aspas) e teclar <ENTER>, você vai ver que o interpretador do MySQL vai começar a processar o arquivo, gerando várias saídas. Se tudo der certo, uma base de dados chamada “sakila” será criada, bem como todas as tabelas da sua estrutura. Se quiser verificar que a base de dados foi criada, basta dar o comando “show databases;” (sem as aspas) para listar as bases de dados existentes.

Como exercício, da mesma forma que você carregou o arquivo sakila-schema.sql, você deve agora carregar o sakila-data.sql. Novamente os comandos serão processados e agora, além da estrutura da base de dados (criada no passo anterior), temos também vários dados para podermos utilizar em nossas consultas SQL e consequentemente em nossos relatórios, mas antes de partirmos para os relatórios, vamos falar um pouquinho da base de dados sakila. A seguir, uma imagem da estrutura da base, carregada no MySQL Workbench (clique na imagem para ampliar).

 

Figura 2

Estrutura da base de dados sakila

 

Na base de dados sakila estão modeladas diversas tabelas que representam os dados de uma locadora de DVDs fictícia. Não vou ficar comentando cada tabela, pois apenas com a leitura do diagrama você vai conseguir entender os relacionamentos entre as elas, afinal, se você trabalha com bases de dados isso devere ser trivial :D.

Pronto! Agora sim! Vamos aos relatórios! Abra o NetBeans (se ainda não estiver aberto) e carregue o projeto que criamos na Parte 1 do tutorial. Caso você só tenha lido o tutorial, não se preocupe, você pode baixar o projeto feito na Parte 1 neste link. Com o projeto aberto, expanda o nó “Relatórios” do projeto. Você vai notar a existência do default package (pacote padrão). É nele que vamos criar os arquivos de código fonte dos nossos relatórios. Sendo assim, clique com o botão direito neste pacote e escolha New -> Other… (Novo -> Outro…). Veja a Figura abaixo.

 

Figura 3

Novo arquivo

 

Ao clicar em Other… a janela de novo arquivo arquivo será aberta. Em Categories (Categorias) escolha Report (Relatório) e em File Types (Tipos de Arquivo) escolha Empty report (Relatório vazio) e clique em Next (Próximo). Veja a Figura abaixo.

 

Figura 4

Novo arquivo fonte de relatório

 

Ao clicar em Next, preencha o campo File Name (Nome do Arquivo) com “Clientes” (sem as aspas) e clique em Finish (Finalizar/Terminar). Veja a Figura abaixo.

 

Figura 5

Criando o novo arquivo fonte de relatório

 

Ao clicar em Finish, o arquivo de código fonte do relatório será criado (Clientes.jrxml) e a interface de edição do relatório será aberta, com o arquivo Clientes.jrxml carregado. Note que neste primeiro relatório, vamos simplesmente listar os dados de todos os clientes, que por sua vez estão contidos na tabela customer da base de dados sakila.

Vamos agora conhecer a interface do editor do iReport. A Figura abaixo contém diversas áreas numeradas que serão explicadas logo à seguir.

 

Figura 6

Interface gráfica do editor WYSIWYG do iReport

 

  1. Gerenciamento de Datasources (Fontes de Dados): A área 1, destacada em amarelo, é utilizada para gerenciar os datasources que podem ser utilizados nos testes dos relatórios que estão sendo criados;
  2. Área de edição do relatório: A área 2, destacada em azul escuro, contém o editor visual do relatório (descrito no item 3 a seguir), além de alguns botões onde podemos visualizar o editor WYSIWYG (Design), o código fonte do relatório (XML) e visualizar o relatório sendo executado (Preview), além de podermos configurar a forma que o relatório vai obter os dados que serão obtidos atravéz do datasource utilizado (primeiro botão após o botão Preview);
  3. Editor do relatório: Essa área é utilizada para editar o formato do relatório, onde cada texto vai aparecer, quais bandas ele vai ter, etc. Existem vários tipos de bandas. As que são exibidas por padrão são:
    • Title: correponde ao cabeçalho do relatório. Aparece somente na primeira página;
    • Page Header: cabeçalho da página. Aparece em todas as páginas;
    • Column Header: cabeçalho das colunas. Aparece em todas as páginas;
    • Detail: exibe os dados provenientes do datasource, ou seja, são os dados que queremos mostrar no relatório;
    • Column Footer: rodapé das colunas. Aparece em todas as páginas;
    • Page Footer: rodapé da página. Aparece em todas as páginas;
    • Summary: resumo do relatório. Aparece apenas na última página.
  4. Paleta de componentes: Esta área contém os componentes que podem ser utilizados para montar o relatório;
  5. Propriedades: Área utilizada para exibir e editar as propriedades de um componente que esteja selecionado;
  6. Report Inspector: É nesta área que as configurações do relatório são acessadas, como quais parâmetros ele contém, quais os campos que são utilizados, quais bandas estão visíveis, etc.;
  7. Saída (Output): Nesta área são exibidos as saídas da execução e compilação do relatório corrente.

Se a explicação de cada área ficou confusa, não se preocupe. Você vai entender tudo daqui a pouco. Vamos começar então a mexer no nosso primeiro relatório. Primeiro então temos que configurar um datasource que vai trazer os dados de algum lugar. Note que esse datasource é utilizado apenas durante a edição do relatório. Quando formos executar o relatório dentro de um programa, teremos que passar o datasource programaticamente, mas isso nós vamos aprender depois. Pois bem, clique então no botão Report datasources (Fontes de dados do relatório). Veja a Figura abaixo.

 

Figura 7

Botão Report datasources

 

Ao clicar neste botão, a janela de gerenciamento de datasources será exibida. Por padrão, apenas um datasource vai estar configurado no iReport. Este datasource, o Empty datasource (fonte de dados vazia) pode ser utilizado para visualizarmos apenas a estrutura do relatório. Vamos então criar o datasource para a base de dados sakila. Para isso, clique no botão New (Novo). Veja a Figura abaixo.

 

Figura 8

Gerenciador de datasources

 

Ao clicar em New, uma nova janela será exibida. Nesta janela escolhemos o tipo de datasource que vamos utilizar. No nosso caso, é um Database JDBC connection, pois iremos obter os dados no nosso relatório utilizando uma query SQL, que por sua vez vai ser executada através de uma conexão JDBC. Por padrão, este tipo de datasource é o que vai estar selecionado no assistente. Caso não esteja selecionado, selecione-o e clique em Next. Veja a Figura abaixo.

 

Figura 9

Escolhendo o tipo de datasource

 

Ao clicar em Next, a janela de configuração do datasource vai ser exibida. Nela precisamos preencher alguns campos:

  • Name: nome do datasource. Utilize “Sakila – JDBC” (sem as aspas);
  • JDBC Driver: o tipo de driver que vamos utilizar. No nosso caso, é o driver do MySQL;
  • JDBC URL: endereço do banco de dados que queremos utilizar. No nosso caso é “jdbc:mysql://localhost/sakila” (sem as aspas);
  • Username: nome do usuário. Estamos trabalhando em um ambiente de desenvolvimento, então vamos usar o root;
  • Password: senha do usuário. No meu caso a senha do root é root também;
  • Save password: salvar a senha utilizada. Pode deixar marcada essa opção, se não, toda hora que fizermos uma conexão, o iReport vai pedir a senha do usuário.
  • Obs: os campos Server Address e Database são preenchidos apenas se você quiser usar o Wizard (botão Wizard) para preencher a URL de conexão. Como já sabemos o formato da URL para o MySQL e já preenchemos o endereço, não precisamos usar esses campos do Wizard.

Com tudo preenchido, clique em Save (Salvar). Veja a Figura abaixo.

 

Figura 10

Configurando o novo datasource

 

Ao salvar o datasource, a janela anterior será exibida, mostrando que o novo datasource foi configurado e está marcado como default (Padrão). Clique em Close para fechar a janela. Fazendo isso, você vai ver que o combobox que contém os datasources estará exibindo o datasource padrão, ou seja, o “Sakila – JDBC” que criamos. Isso significa que quando formos criar uma query SQL no nosso relatório ou formos executá-lo para testar (Preview), o iReport vai usar o datasource que estiver selecionado neste combo. Novamente, reforço a ideia que este datasource é utilizado APENAS durante o desenvolvimento do relatório, seja na montagem de queries ou testes do relatório. Quando formos colocar o relatório para ser aberto em nosso programa, teremos que passar programaticamente o datasource que o nosso relatório irá usar. Veja a Figura abaixo.

 

Figura 11

Datasource "Sakila - JDBC" selecionado

 

Muito bem. Configuramos o datasource que vamos utilizar e ele já está selecionado. Vamos agora ao relatório. Vamos agora editar a query SQL do Clientes.jrxml. Para isso, clique no botão que está destacado na Figura abaixo.

 

Figura 12

Acessando o editor de queries

 

Ao clicar no botão, a janela mostrada na Figura abaixo vai ser exibida.

 

Figura 13

Editor de queries

 

Nesta janela é que vamos editar a query do nosso relatório. Ao inserir a query, os campos que ela retorna serão automaticamente carregados. Para este relatório, queremos listar todos os Clientes da locadora de DVDs, então iremos usar a query “SELECT * FROM customer;” (sem as aspas). Assim que a query for preenchida e executada com sucesso, todos os campos que  a execução dela retornar serão carregados, sendo que seus respecitvos tipos também serão obtidos. Veja a Figura abaixo.

 

Figura 14

Execução da query e carga dos campos

 

Observando a Figura acima, você pode perceber que ao executar a query informada foram obtidos os campos customer_id (tipo Integer), store_id (tipo Integer), first_name (tipo String) e assim por diante. Ao clicar em OK, esses campos (note que estão selecionados) serão inseridos na definição do relatório, bastando agora nós pegarmos esses campos e inserir cada um deles, ou os que nos forem relevantes, no Design do relatório. Para pegar os campos, expanda o nó Fields (Campos) do Report Inspector. Veja a Figura abaixo.

 

Figura 15

Campos inseridos na definiçao do relatório

 

Suponha que queremos mostrar em nosso relatório apenas o nome, o sobrenome e o e-mail de cada cliente. Sendo assim, selecione primeiro o campo “first_name” e arraste para a banda Detail do relatório. Veja a Figura abaixo.

 

Figura 16

Iniciando a criação do relatório

 

Note que onde você soltou o campo fist_name (na bada Detail), foi inserida uma caixa de texto do tipo Text Field (veja a paleta de componentes) com o valor $F{first_name}, onde $F denota o uso de um campo (F de Field) e o valor entre chaves é o nome do campo. Na banda Column Header foi inserida outra caixa de texto, só que apenas com o valor first_name. Essa caixa de texto inserida no cabeçalho da coluna é do tipo Static Text, ou seja, é um texto estático. Vamos mudar então o valor dessa segunda caixa para “Nome” (sem as aspas) e organizar tanto a caixa de texto estático quanto a de texto dinâmico. Veja a Figura abaixo.

 

Figura 17

Organizando o relatório

 

Vamos testar agora o relatório, afinal, queremos vê-lo funcionando. Procure pelo botão Preview e clique nele. O relatório será compilado e exibido. Veja a Figura abaixo.

 

Figura 18

Visualizando o relatório

 

Legal, já estamos vendo os dados que vem do banco de dados no relatório, mas afinal, qual o motivo de um nome estar tão distante do outro? O motivo é que a banda Detail está muito alta. A altura da banda Detail sempre vai ser replicada para cada registro encontrado. Então, para melhorar isso, vamos diminuir a altura da banda Detail. Volte então na visualização no Designer (usando o botão Designer), selecione a linha azul no limite inferior da banda Detail e arraste até chegar na altura desejada. Caso você queira que a banda fique com o tamanho do conteúdo dela (o que tem dentro dela), clique duas vezes na linha azul. A banda vai ser redimensionada automaticamente. Veja a Figura abaixo.

 

Figura 19

Banda detail redimensionada

 

Teste novamente o relatório. Você vai notar que agora os nomes estão mais perto um do outro. Agora, como exercício, arraste os campos last_name e email para o relatório (na banda Detail) e organize-os. Note que você pode redimensionar cada campo tanto na largura quanto na altura. Outra tarefa é voltar ao editor da query do relatório e inserir o comando ORDER BY na query, para ordenar os registros pelo nome (first_name). Quando fizer tudo isso e mandar visualizar, o resultado deve ser algo parecido com o apresentado na Figura abaixo.

 

Figura 20

Relatório atualizado

 

Vamos deixar agora o relatório um pouco mais apresentável. Da mesma forma que fez com a banda Detail, diminua agora banda Colum Header. Clique com o botão direito na banda Page Header e escolha Delete band. Na banda Title, insira uma caixa de texto estática, troque o valor do texto para “Clientes Cadastrados” (sem as aspas). Utilize o editor de propriedades do componente (área 5 da Figura onde são apresentadas as áreas do iReport) para configurar o tamanho da fonte do componente para 26, negrito (bold), configure o alinhamento horizontal (horizontal alignment) para centro (center) e o alinhamento vertical (vertical alignment) para meio (middle). Expanda o campo de texto para ele ocupar toda a largura da página do relatório. Configure os campos que definem as colunas da banda detail (aqueles campos de texto contidos no cabeçalho) para ficarem em negrito. Execute novamente o relatório. Na Figura abaixo é mostrado como seu relatório deve ter ficado no Preview.

 

Figura 21

Editando o relatório

 

Para finalizar o design do nosso relatório, vamos adicionar o número de página na banda Page Footer. No Report Inspector, procure pelo nó Variables e expanda-o. Uma das variáveis pré-configuradas se chama PAGE_NUMBER. Arraste-a para o lado direito da banda Page Footer e configure o alinhamento horizontal do texto para a direita (right). Dimensione a altura da banda Page Footer para ficar do tamanho do campo que foi inserido. Delete as bandas Column Footer e Summary (clique com o botão direito e escolha delete band em cada uma delas). O design do relatório deve estar como o da Figura abaixo.

 

Figura 22

Design final do relatório

 

Teste seu relatório e veja que o número da página será exibido no final de cada página. Note que o campo do campo de texto que foi inserido (um campo dinâmico) é $V{PAGE_NUMBER}, sendo que o $V denota o uso de uma variável e o valor entre chaves é o nome de variável. Ainda vamos ver como criar variáveis e parâmetros manualmente (ainda não falei dos parâmetros).

Agora que já conseguimos criar nosso primeiro relatório, nós vamos aprender um detalhe muito importante que se conhecido, muitos problemas podem ser evitados. Quando usamos uma query no editor de queries e o iReport cria automaticamente os campos (Fields) para nós, ele atributi automaticamente um tipo para cada campo, dependendo, é claro, do tipo que aquele campo tem quando ele é obtido. Ou seja, first_name é um campo VARCHAR na tabela customer, então quando esse valor é obtido no iReport é criado um campo, com o nome first_name (o nome que vem por padrão) do tipo String, que é a representação para cadeias de caracteres em Java.

Quando queremos usar um campo no nosso relatório, nós podemos arrastar ele diretamente do Report Inspector para o relatório que o iReport vai se preocupar em criar um campo dinâmico (Text Field na paleta) e inserir o valor $F{nome_do_campo} dentro do campo dinâmico. Um detalhe que não vemos acontecer, justamente porque o iReport faz automáticamente, é a configuração do tipo do campo dinâmico. O tipo do campo dinâmico tem que ser SEMPRE igual ao tipo do campo que é utilizado.

Para entender o que eu falei, vamos fazer o seguinte teste. No relatório que estamos criando, arraste para a banda Detail – na frente do campo para e-mail – um campo dinâmico (Text Field na paleta). Por padrão, o valor do campo vai ser $F{field}. Se você tentar executar o relatório, o iReport vai reclamar, falando que o campo “field” não existe. Isso é uma verdade, visto que nenhum campo com o nome field foi criado não é mesmo? Vamos substituir o valor field para customer_id. O campo vai ficar então com o valor $F{customer_id}. Tente executar o relatório para ver o que acontece. Funcionou? Não! Mas qual o motivo? O erro diz “Cannot cast from Integer to String“. Isso quer dizer que o valor do campo customer_id é um Integer e o JasperReports está sendo instruido a fazer um cast explícito de Integer para String. Mas de onde vem essa String? Lembram que falei que o campo dinâmico (Text Field) tem que ter o mesmo tipo de um campo? Ou seja, o campo customer_id é do tipo Integer (inferido na criação da query), enquanto o campo dinâmico que está inserido no relatório é do tipo String. O que temos que fazer? Mudar o tipo do campo dinâmico. Para isso, selecione o campo dinâmico que está na banda Detail e procure pela propriedade “expression class” na guia de propriedades do editor. O valor que estará lá é java.lang.String. Vamos mudar para java.lang.Integer. Ao fazer isso, teste novamente o relatório. Agora funcinou não é? Então, tenha sempre em mente que o tipo de um campo dinâmico tem que SEMPRE SER DO MESMO TIPO que o campo ($F), a variável ($V), o parâmetro ($P), ou o resultado de uma expressão que for executada na propriedade Text Field Expression do campo dinâmico.

Legal, agora nós vamos fazer esse relatório ser executado a partir de um programa. Lembram que eu falei que para um relatório ser executado, nós precisaríamos passar o datasource que nós queremos que ele use? Pois bem, no nosso caso, o nosso datasource é uma conexão JDBC (java.sql.Connection), sendo assim, vamos precisar criar conexões  para passar para a engine de execução do relatório. Vamos então criar uma fábrica de conexões para utilizarmos. Essa fábrica é uma classe, com o nome ConnectionFactory. Para criar a classe, primeiramente vá para a aba Projects do NetBeans (canto superior esquerdo) expanda o nó Source Packages (pacotes de código fonte) do nosso projeto, e no pacote “tutorialrelatorios”, crie um pacote chamado “jdbc” (sem as aspas). Dentro então do pacote jdbc, crie uma classe chamada ConnectionFactory. Segue o código comentado da classe.

tutorialrelatorios.jdbc.ConnectionFactory.java

package tutorialrelatorios.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * Uma fábrica de conexões.
 *
 * @author David Buzatto
 */
public class ConnectionFactory {

    /*
     * Este bloco estático será executado assim que esta classe for carregada,
     * sendo assim, será executado apenas uma vez.
     */
    static {
        try {
            /*
             * Carrega a classe com.mysql.jdbc.Driver, que é a implementação
             * do driver JDBC para o MySQL.
             */
            Class.forName( "com.mysql.jdbc.Driver" );

            // caso a classe não seja encontrada
        } catch ( ClassNotFoundException exc ) {

            /*
             * Como log usaremos o stacktrace das excessões, mas recomendo
             * que para um projeto real você utilize algum mecanismo de log
             * melhor, como o Log4J por exemplo.
             */
            exc.printStackTrace();

        }
    }

    /**
     * O método getConnection retorna uma conexão com o banco de dados baseado
     * nos parâmetros fornecidos.
     *
     * @param url O endereço da base de dados.
     * @param usuario O usuário que tem permissão na base de dados especificada.
     * @param senha A senha do usuário especificado
     * @return Uma conexão com o banco de dados especificado na url.
     * @throws SQLException Caso ocorra algum problema durante a conexão.
     */
    public static Connection getConnection(
            String url,
            String usuario,
            String senha ) throws SQLException {

        // retorna a conexão a partir do método getConnection de DriverManager
        return DriverManager.getConnection( url, usuario, senha );

    }

    /**
     * Obtém uma conexão para a base de dados sakila.
     *
     * @return Uma conexão para a base de dados sakila.
     * @throws SQLException Caso ocorra algum problema durante a conexão.
     */
    public static Connection getSakilaConnection() throws SQLException {

        return getConnection(
                "jdbc:mysql://localhost/sakila",
                "root",
                "root" );

    }

}

Usando o método getSakilaConnection() iremos então obter conexões para a base de dados sakila, sendo que essa conexão será utilizada pelo JasperReports para executar as queries que forem definidas nos relatórios.

Além da fábrica de conexões, vamos criar também uma classe que conterá métodos utilitários para abrir relatórios. Para isso, crie um pacote chamado “utils” (sem as aspas) dentro do pacote “tutorialrelatorios”. Dentro do pacote criado, crie uma classe com o nome de ReportUtils. Segue o código comentado da classe.

tutorialrelatorios.util.ReportUtils.java

package tutorialrelatorios.util;

import java.awt.BorderLayout;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Map;
import javax.swing.JFrame;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.swing.JRViewer;

/**
 * Classe com métodos utilitários para executar e abrir relatórios.
 *
 * @author David Buzatto
 */
public class ReportUtils {

    /**
     * Abre um relatório usando uma conexão como datasource.
     *
     * @param titulo Título usado na janela do relatório.
     * @param inputStream InputStream que contém o relatório.
     * @param parametros Parâmetros utilizados pelo relatório.
     * @param conexao Conexão utilizada para a execução da query.
     * @throws JRException Caso ocorra algum problema na execução do relatório
     */
    public static void openReport(
            String titulo,
            InputStream inputStream,
            Map parametros,
            Connection conexao ) throws JRException {

        /*
         * Cria um JasperPrint, que é a versão preenchida do relatório,
         * usando uma conexão.
         */
        JasperPrint print = JasperFillManager.fillReport(
                inputStream, parametros, conexao );

        // abre o JasperPrint em um JFrame
        viewReportFrame( titulo, print );

    }

    /**
     * Abre um relatório usando um datasource genérico.
     *
     * @param titulo Título usado na janela do relatório.
     * @param inputStream InputStream que contém o relatório.
     * @param parametros Parâmetros utilizados pelo relatório.
     * @param dataSource Datasource a ser utilizado pelo relatório.
     * @throws JRException Caso ocorra algum problema na execução do relatório
     */
    public static void openReport(
            String titulo,
            InputStream inputStream,
            Map parametros,
            JRDataSource dataSource ) throws JRException {

        /*
         * Cria um JasperPrint, que é a versão preenchida do relatório,
         * usando um datasource genérico.
         */
        JasperPrint print = JasperFillManager.fillReport(
                inputStream, parametros, dataSource );

        // abre o JasperPrint em um JFrame
        viewReportFrame( titulo, print );

    }

    /**
     * Cria um JFrame para exibir o relatório representado pelo JasperPrint.
     *
     * @param titulo Título do JFrame.
     * @param print JasperPrint do relatório.
     */
    private static void viewReportFrame( String titulo, JasperPrint print ) {

        /*
         * Cria um JRViewer para exibir o relatório.
         * Um JRViewer é uma JPanel.
         */
        JRViewer viewer = new JRViewer( print );

        // cria o JFrame
        JFrame frameRelatorio = new JFrame( titulo );

        // adiciona o JRViewer no JFrame
        frameRelatorio.add( viewer, BorderLayout.CENTER );

        // configura o tamanho padrão do JFrame
        frameRelatorio.setSize( 500, 500 );

        // maximiza o JFrame para ocupar a tela toda.
        frameRelatorio.setExtendedState( JFrame.MAXIMIZED_BOTH );

        // configura a operação padrão quando o JFrame for fechado.
        frameRelatorio.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );

        // exibe o JFrame
        frameRelatorio.setVisible( true );

    }

}

Não vamos criar uma interface gráfica para executar nosso relatório, pois vamos abri-lo diretamente da classe Main do projeto. Perceba que o código usado na classe Main é o mesmo que você vai utilizar, por exemplo, para abrir o relatório a partir do clique de um botão, mas antes disso, mais alguns detalhes. Quando fazemos o preview do relatório, o iReport invoca o compilador do JasperReports para compilar o arquivo .jrxml em um arquivo .jasper, para então executar o arquivo .jasper, abrindo assim o relatório. Você deve ter percebido que agora, além do arquivo Clientes.jrxml, existe também o arquivo Clientes.jasper na nossa pasta de definições de relatórios. Se você quiser compilar manualmente um relatório sem entrar no preview do mesmo, basta ir no Report Inspector, com o relatório desejado aberto, clicar com o botão direito no nó raiz do relatório, que por padrão tem o nome de “report name” (nome do relatório) e escolher Compile Report (Compilar Relatório).

Vamos então executar nosso relatório a partir do método main da classe Main do nosso projeto. Segue o código comentado da classe.

package tutorialrelatorios;

import java.io.InputStream;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import net.sf.jasperreports.engine.JRException;
import tutorialrelatorios.jdbc.ConnectionFactory;
import tutorialrelatorios.util.ReportUtils;

/**
 * Ponto de entrada do projeto.
 *
 * @author David Buzatto
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        new Main().abrirRelatorioClientes();
    }

    public void abrirRelatorioClientes() {

        /*
         * Obtendo o arquivo do relatório.
         * Note que estamos utilizando um InputStream para obter o arquivo que
         * está dentro do nosso projeto. Fazendo isso, não teremos problema
         * quando nosso projeto for empacotado em um .jar.
         *
         * Note que o caminho do .jasper inicia com /, ou seja, a raiz da
         * localização das classes compiladas do nosso projeto
         * (o pacote default).
         *
         * Utilize a aba Files (canto superior esquerdo) e veja que os arquivos
         * .jasper e .jrxml são copiados para o diretório /build/classes
         * e por consequencia para o .jar que for criado.
         *
         * Se não os estiver vendo, mande dar um Clean and Build no projeto
         * (botão direito no nó raiz do projeto, Clean and Build (Limpar e Construir)
         *
         */
        InputStream inputStream = getClass().getResourceAsStream( "/Clientes.jasper" );

        // mapa de parâmetros do relatório (ainda vamos aprender a usar)
        Map parametros = new HashMap();

        try {

            // abre o relatório
            ReportUtils.openReport( "Clientes", inputStream, parametros,
                    ConnectionFactory.getSakilaConnection() );

        } catch ( SQLException exc ) {
            exc.printStackTrace();
        } catch ( JRException exc ) {
            exc.printStackTrace();
        }

    }

}

Tente executar o projeto teclando F6. Uma excessão será lançada (java.lang.ClassNotFoundException: com.mysql.jdbc.Driver). Isso se deve ao fato de que nós ainda não colocamos o driver do MySQL no nosso projeto! Clique então em Libraries com o botão direito e escolha Add Library. Como as bibliotecas do nosso projeto ficam inseridas no projeto e não nas configurações do NetBeans, temos que criar a biblioteca como fizemos com o JasperReports e suas dependências. Mas ao invés de fazer todo aquele processo na mão, nós podemos importar bibliotecas que estão configuradas no NetBeans sendo que o driver do MySQL é uma delas. Sendo assim, na janela que se abriu, clique no botão Import. Ao fazer isso, a janela Import Library irá aparecer. Procure pela biblioteca “MySQL JDBC Driver”, selecione-a e clique no botão Import Library. Com isso a biblioteca do driver do MySQL será importada para o projeto. Você vai ver ela na janela que apareceu novamente. Selecione-a e clique em Add Library. Após fazer isso, tecle F6 novamente para executar o projeto.

Agora sim, uma janela com o relatório será aberta. Experimente utilizar os controles da janela, tente salvar o relatório em vários formatos, etc. Após brincar um pouco com isso, feche a janela e vamos a mais algumas explicações para finalizarmos esta parte do tutorial.

Ao ler os comentários da classe Main, você deve ter percebido duas coisas. Primeiro, o arquivo .jrxml está sendo copiado para a estrutura compilada do nosso projeto (build) e por consequência, o arquivo .jrxml vai ser empacotado no .jar, sendo que só queremos distribuir o arquivo compilado (.jasper). Segundo, ainda não aprendemos a utilizar o mapa de parâmetros e ainda não foi explicado qual a sua utilidade.

Em relação ao primeiro problema, para excluir certos tipos de arquivos do processo de build, abra as propriedades do projeto e localize o nó Packaging, dentro do nó Build. Ao clicar nesta opção, você vai ver um campo chamado “Exclude From JAR File“. Neste campo, você informa o padrão de nome dos arquivos que você NÃO QUER que sejam inseridos no build. Por padrão, o valor é “**/*.java,**/*.form”, ou seja, qualquer arquivo, de qualquer diretório do projeto que tenha extensão “java” e qualquer arquivo, de qualquer diretório do projeto que tenha extensão “form”. Precisamos inserir então nessa lista os arquivos de extensão “jrxml”. Para fazer isso, basta adicionar um item na lista com o valor “**/*.jrxml” (sem as aspas). O valor final do campo deve ser “**/*.java,**/*.form, **/*.jrxml” (sem as aspas). Ao fazer isso, clique em OK e mande limpar e refazer o build no projeto (botão direito no nó raiz do projeto, opção Clean and Build – Limpar e Construir). Vá novamente na aba Files (canto superior esquerdo) e expanda a pasta /build/classes. Você vai ver que agora somente o arquivo .jasper está sendo copiado para o build.

Tudo bem até aqui? Espero que sim. Estamos acabando. Agora vamos a parte final, os parâmetros.

Quando criamos listagens de dados em relatórios, normalmente nós queremos filtrar tais dados, nos baseando em alguma restrição. Imagine que no nosso relatório de clientes, nós queiramos filtrar os clientes existentes a partir do primeiro nome deles. Para isso, teremos que inserir uma restrição na query do relatório para comparar o primeiro nome com o valor que queremos filtrar. Modificar a query é fácil, mas nós precisamos que esse valor que será utilizado na query seja dinâmico, ou seja, a cada vez que executarmos o relatório, queremos que o valor de restrição seja diferente. Para utilizar esses valores dinâmicos no iReport, nós usamos os chamados parâmetros. Vamos então fazer uma cópia do nosso primeiro relatório para inserirmos esse tipo de restrição.

Na aba Projects, procure pelo arquivo Clientes.jrxml na pasta de definições de relatórios. Clique com o botão direito nele e escolha Copy (Copiar). Clique com o botão direito no pacote default da pasta de definições de relatórios e escolha Paste (Colar). O arquivo será colado com o nome de Clientes_1.jrxml. Selecione o arquivo, tecle F2 (ou botão direito, rename – renomear) e troque o nome para ClientesPorNome.jrxml. Ao fazer isso, abra o ClientesPorNome.jrxml no editor de relatórios.

Com o arquivo aberto, vá no Report Inspector e procure pela banda Page Header que estará sendo exibida em cinza claro. Está assim porque ela foi removida lembra? Para fazer ela aparecer novamente no relatório, clique com o botão direito nela e escolha Add Band (Adicionar Banda). Ainda não vamos mexer com essa banda, mas só para adiantar, ela vai ser utilizada para mostrar o filtro que estamos fazendo.

Ainda no Report Inspector, procure pelo nó Parameters (Parâmetros) e expanda-o. Você vai ver que existem diversos parâmetros pré-configurados no nosso relatório. Nós queremos criar mais um. Para isso, clique com o botão direito em Parameters e escolha Add Parameter. Um novo parâmetro, com o nome “parameter1” vai ser criado. Selecione-o e troque o nome dele para “primeiroNome”. Perceba que ao selecionar um parâmetro, o editor de propriedades do iReport mostra diversas propriedades, entre elas a classe do parâmetro (Parameter Class). Da mesma forma que os campos e variáveis do relatório, os parâmetros também precisam de um tipo. Este parâmetro que estamos criando vai ser utilizado no filtro da query, ou seja, ele vai conter o valor que queremos utilizar como filtro. Um nome é uma String não é? Então a classe desse parâmetro é a java.lang.String mesmo, mas imagine que queiramos comprar um número. Então a classe teria que ser de algum tipo numérico ok?

Note também que a propriedade “Use as prompt” deve estar selecionada. Essa propriedade é utilizada para que quando formos dar o preview no relatório, seja aberto um diálogo para pedir o valor que queremos que o parâmetro assuma naquela execução. Execute o relatório para ver isso acontecer. Veja a Figura abaixo.

 

Figura 23

Prompt de parâmetro

 

Mesmo que você informe qualquer valor para o parâmetro, o resultado do relatório vai ser o mesmo, afinal, não inserimos o parâmetro na query do relatório não é mesmo? Vamos fazer isso agora. Abra o editor de queries do relatório e edite a query para ficar assim:

SELECT * FROM customer
WHERE first_name LIKE $P{primeiroNome}
ORDER BY first_name;

Após editar, clique em OK. Note que foi inserido uma cláusula WHERE na query, comparando o campo first_name com o valor do parâmetro, usando o operador LIKE. Note que para utilizarmos o valor do parâmetro, utilizamos a notação “$P{nomeDoParametro}”. Imagine que na hora da execução, o parâmetro assuma o valor “D%”, ou seja, queremos qualquer cliente com o nome que inicie com a letra D. A query na hora da execução ficaria assim:

SELECT * FROM customer
WHERE first_name LIKE 'D%'
ORDER BY first_name;

Perceberam o que estamos fazendo? Note que as aspas simples serão inseridas automaticamente pelo JasperReports na hora da execução, pois o parâmetro primeiroNome é uma String. Teste o relatório e passe no valor do paramâmetro como ‘D%’ (sem as aspas). O resultado deve estar parecido com o da Figura abaixo.

 

Figura 24

Resultado do relatório passando D% no valor do parâmetro

 

Legal não é? Então caso você queira inserir mais restrições no relatório, basta criar mais parâmetros e ir modificando a query. Faça mais alguns testes ;). Vamos agora mostrar para o usuário qual foi a restrição que foi pedida no relatório. Lembram da banda Page Header que colocamos novamente no relatório? Insira nela um campo de texto estático (Static Text) e preencha-o com o valor “Com nome:” (sem as aspas), alinhe o texto à direita e escolha para ficar em negrito (bold). Na frente deste campo, insia um campo dinâmico (Text Field) e edite seu valor para “$P{primeiroNome}” (sem as aspas). Percebeu o que estamos fazendo? Vamos mostrar o valor do parâmetro no relatório também! Lembre-se que o tipo do campo dinâmico tem que ser o mesmo do valor que ele vai mostrar, no caso uma String, que é o tipo do parâmetro “primeiroNome”. Coloquei também algumas linhas (componente Line da paleta) no relatório para ficar mais bonitinho ;). Veja a Figura abaixo para ver como ficou.

 

Figura 25

Design do relatório finalizado

 

Muito bem! Agora, para que este novo relatório funcione corretamente quando formos utilizá-lo no nosso programa, nós precisamos passar o parâmetro que foi configurado não é mesmo? Lembram do mapa de parâmetros que foi criado? Do tipo Map<String, Object>? É nele que passamos o valor dos parâmetros. A chave do mapa é uma String, que vai corresponder ao nome do parâmetro (no nosso caso, primeiroNome). O valor do item do mapa é um Object, mas precisamos passar instâncias de objetos que casem com o tipo do parâmetro configurado no relatório. No nosso caso, primeiroNome é do tipo String lembram? Então o objeto que tem que ser passado no valor do item do mapa precisa ser uma String, por causa do tipo do parâmetro como já falei. Veja o código alterado do método abrirRelatorioClientes() da classe Main.

public void abrirRelatorioClientes() {

    // note que estamos chamando o novo relatório
    InputStream inputStream = getClass().getResourceAsStream( "/ClientesPorNome.jasper" );

    // mapa de parâmetros do relatório
    Map parametros = new HashMap();

    /*
     * Insere o parâmetro primeiroNome no mapa, com o valor F%
     * ou seja, todos os clientes que tenham primeiro nome começando
     * com a letra F.
     */
    parametros.put( "primeiroNome", "F%" );

    // outros possíveis parâmetros aqui...

    try {

        // abre o relatório
        ReportUtils.openReport( "Clientes", inputStream, parametros,
                ConnectionFactory.getSakilaConnection() );

    } catch ( SQLException exc ) {
        exc.printStackTrace();
    } catch ( JRException exc ) {
        exc.printStackTrace();
    }

}

Perceba que caso existam mais parâmetros a serem enviados ao relatório, eles devem ser inseridos no mesmo mapa. Outro detalhe é que estamos apenas fazendo um exemplo e passando um valor fixo no parâmetro. Em um caso real, o valor a String “F%” viria de um campo de texto ou qualquer outro componente da sua interface gráfica.

Com isso terminamos a segunda parte do nosso tutorial. Espero que tenham gostado :). Na próxima parte iremos aprender a trabalhar com os “temidos” subrelatórios :D. Digo “temidos” porque são um pouco chatos de usar e se você não souber realmente o que está fazendo e como eles funcionam, você pode ficar um dia inteiro tentando fazer funcionar e não conseguir 😦

Neste link você pode fazer o download do código fonte do projeto até o momento.

Então é isso ai. Um grande abraço!

Parte 1Parte 2Parte 3Parte 4Parte 5


Parte 1Parte 2Parte 3Parte 4Parte 5

Olá a todos. Quem acompanha o blog sabe que no último post – há duas semanas atrás – eu pedi que vocês sugerissem o tema para o próximo tutorial. Dentre os temas que eu listei, o de criação de relatórios em Java foi o que ganhou. Este tutorial, como o de Flex e Java, vai ser dividido em algumas partes. Eu poderia escrever em apenas um post tudo sobre o tema, mas preferi dividir o tutorial em várias partes pequenas – menores que os tutoriais sobre Flex – para não ficar muito cansativo.

Nesta primeira parte (provavelmente a maior de todas), iremos aprender sobre o framework para criação relatórios que iremos utilizar, o JasperReports, entendendo seu funcionamento, aprendendo a obter e instalar o iReport, que é o editor de relatórios e preparar nosso ambiente de desenvolvimento. Irei novamente utilizar o NetBeans como IDE de desenvolvimento, mas você que usa o Eclipse ou qualquer outra IDE Java não vai ter dificuldades para seguir o tutorial.

Na segunda parte, iremos criar uma base de dados de teste para popularmos nossos relatórios e vamos aprender como criar, compilar e utilizar os relatórios criados em aplicações desktop. Na terceira parte, iremos aprender a criar subrelatórios. Na quarta, iremos utilizar fontes de dados – data sources – diferentes, permitindo então que possamos listar dados nos relatórios que não vem somente de consultas SQL. Por fim, na quinta e última parte, iremos aprender como utilizar nossos relatórios em uma página Web. Após todas essas partes, vocês terão a bagagem necessária para caminharem sozinhos 😉

Então, vamos começar!

Existem atualmente na indústria diferentes frameworks e engines para criação e processamento de relatórios. Dentre as soluções pagas, talvez o Crystal Reports seja a mais conhecida. O Crystal Reports é uma solução robusta e que pode ser utilizada com diversas linguagens de programação. Outra solução paga que é muito utilizada por quem trabalha com Delphi é o Quick Reports. Em Java, existem atualmente duas soluções que são mais utilizadas. O JasperReports, que é o framework gratuito mais popular e que iremos utilizar nos nossos tutoriais e o BIRT (Business Intelligence and Reporting Tools) que também é gratuito e é mais utilizado por quem trabalha com o Eclipse.

Como iremos utilizar o JasperReports, vale a pena então entendermos um pouco do seu funcionamento. Quando trabalhamos com ele, nós criamos os arquivos de código fonte dos nossos relatórios utilizando XML, sendo que esse arquivo fonte – de extensão .jrxml – passa por um processo de compilação para gerar gerar arquivos .jasper, que por sua vez são interpretados pela engine do JasperReports. Nós não iremos escrever esses arquivos .jrxml na mão, pois iremos construir nossos relatórios utilizando um editor WYSIWYG (What You See Is What You Get), o iReport. Atualmente, o iReport pode ser obtido em duas versões. Uma delas é standalone (criada usando a NetBeans Platform), não precisando de nenhum outro programa para ser utilizada, enquanto a outra é um plugin para o NetBeans. Iremos utilizar a versão em plugin.

Bem, já falei demais. Chega de blablablá, vamos começar a por a mão na massa, afinal essa é a melhor forma de aprender :D. Primeiro vamos baixar o iReport. Acesse então o endereço http://jasperforge.org/projects/ireport e procure o link de download. Na versão atual do site, para fazer o download, basta clicar no botão “Download iReport Now“. Ao clicar no botão, serão apresentadas as versões disponíveis. Nós vamos escolher a última: Plugin for NetBeans IDE 3.x. A versão atual do iReport é a 3.7.5. Note que a versão do iReport sempre vai ser a mesma do JasperReports.

Quando terminar de baixar, descompacte o arquivo “iReport-3.7.5-plugin.zip” (a versão pode variar dependendo de quando você baixar o pacote). Serão extraídos 4 arquivos com a extensão .nbm (NetBeans Module). Esses arquivos são os pacotes dos plugins que serão instalados no NetBeans. Com o arquivo descompactado, abra então o seu NetBeans. Estou usando a útlima versão, 6.9.1. Com o NetBeans aberto, vá no menu Tools (Ferramentas) e escolha Plugins. Ao abrir o gerenciador de plugins, vá na aba Downloaded (Baixados?). É nessa aba que você instala os arquivos .nbm. Clique em Add Plugins (Adicionar Plugins). Veja a Figura abaixo:

Figura 1

Gerenciador de Plugins

Ao clicar no botão Add Plugins, um diálogo será aberto. Neste diálogo, procure então pelos arquivos .nbm que foram descompactados a partir do pacote que fizemos download. Assim que os encontrar, selecione todos e clique Open (Abrir). Veja a Figura abaixo.

Figura 2

Escolhendo os pacotes

Ao fazer isso, a janela do gerenciador de plugins será exibida novamente, só que agora com os quatro pacotes sendo exibidos. Deixe os quatro pacotes marcados e clique em Install (Instalar). Veja a Figura abaixo.

Figura 3

Gerenciador de Plugins (instalação)

No primeiro passo do instalador, clique em Next (Próximo). Marque o checkbox para concordar com os termos da instalação e clique em Install. Aguarde o final da instalação. Quando a instalação terminar, provavelmente o NetBeans vai pedir para ser reiniciado. Reinicie a IDE. Quando o ambiente estiver aberto novamente, você vai perceber que na barra de ferramentas foi inserido um novo botão e um combobox. Ainda não iremos aprender para que eles servem, por enquanto entenda que eles fazem parte do plugin do iReport. Veja a Figura abaixo.

Figura 4

Barra de ferramentas alterada

Com o iReport instalado, vamos agora preparar a estrutura de uma aplicação que iremos usar nas próximas partes do tutorial. Inicie a criação de um novo projeto no NetBeans escolhendo um projeto do tipo Java Application (Aplicação Java). Veja a Figura abaixo.

Figura 5

Novo projeto

Clique em Next. Em seguida, dê o nome ao projeto, sugiro “TutorialRelatorios” (sem as aspas), escolha onde o projeto vai ser salvo e marque a opção Use Dedicated Folder for Storing Libraries (essa opção vai fazer com que todos os arquivos .jar utilizados fiquem contidos dentro do projeto, sob o diretório “lib”), configure as outras opções de acordo com a Figura abaixo e clique em Finish.

Figura 6

Configurando o novo projeto

Ao clicar em Finish, o projeto vai ser criado. A partir de agora já poríamos começar a criar nossos relatórios no iReport, mas ainda faltam algumas configurações para que possamos fazer esses relatórios executarem dentro de uma aplicação. Para isso, ainda temos que adicionar o JasperReports no nosso projeto, bem como todas as suas dependências, além de termos também que organizar a estrutura do projeto para guardar nossos relatórios.

Primeiro vamos tratar dos .jars. Ao instalar os plugins do pacote do iReport, uma biblioteca padrão com os .jars é criada, mas ela vem com alguns erros, então vamos criar a nossa biblioteca para não termos dores de cabeça depois. Para isso, expanda o nó raiz do projeto e procure pelo nó Libraries (Bibliotecas). Clique com o botão direito e escolha Add Library (Adicionar Biblioteca). Veja a Figura abaixo.

Figura 7

Nova biblioteca

Ao clicar em Add Library, o diálogo para a inserção de bibliotecas será exibido. Neste diálogo, clique no botão Create… (Criar). Veja a Figura abaixo.

Figura 8

Criando uma nova biblioteca

Ao clicar em Create, será exibido um novo diálogo, onde o nome e o tipo da nova biblioteca devem ser definidos. Em Library Name (Nome da Biblioteca), configure como “JasperReports-3.7.5”, lembrando de mudar o número da versão caso esteja utilizando uma versão mais nova. Em Library Type (Tipo da Biblioteca), deixe como Class Libraries (Bibliotecas de Classe). Por fim, clique no botão OK. Veja a Figura abaixo.

Figura 9

Criando uma nova biblioteca

Ao clicar em OK, será exibido o diálogo Customize Library (Personalizar Biblioteca?). É nele que indicaremos os .jars que fazem parte da nossa biblioteca. Quando instalamos o plugin do iReport no NetBeans é criada uma pasta chamada “ireport” dentro do diretório de instalação e é lá que estão os .jars que precisamos. Sabendo disso, clique então no botão Add JAR/Folder (Adicionar JAR/Diretório). Veja a Figura abaixo.

Figura 10

Adicionando os JARs

Com o diálogo aberto, procure pelo diretório onde a instalação do NetBeans foi feita. No meu caso, ele foi instalado em C:\Program Files (x86)\NetBeans 6.9.1\. Dentro deste diretório, procure pelo diretório chamado “ireport” e entre nele. O conteúdo do diretório é exibido na Figura abaixo, sendo que os diretórios que nos interessam são o diretório “libs” e o diretório “modules” destacados na Figura.

Figura 11

libs e modules

Primeiramente, acesse o diretório “libs”. Dentro dele, existe apenas um .jar (para a versão 3.7.5 do iReports). Selecione esse .jar (xalan.jar) e clique em Add JAR/Folder. Veja a Figura abaixo.

Figura 12

Adicionando xalan.jar

Ao clicar, o NetBeans vai perguntar se você deseja mesmo criar um diretório dentro do diretório “libs” (aquele que vai guardar nossas bibliotecas) com o nome de JasperReports-3.7.5 (o nome que demos para a biblioteca, lebram?). Responda Sim. Veja a Figura abaixo.

Figura 13

Alerta de criação de novo diretório

Ao aceitar a criação do novo diretório, a janela Customize Libraries voltará a aparecer, agora com o xalan.jar sendo mostrado na lista de .jars. Além da pasta “libs”, precisamos adicionar também os .jars que estão dentro da pasta “modules”. Então clique novamente no botão Add JAR/Folder da janela Customize Libraries e entre no diretório “modules”. Dentro dele existirão alguns .jars, mas não são eles que queremos. Note que além dos .jars, existe um diretório chamado “ext”. Entre nele. Neste diretório estão TODAS as dependências do JasperReports. Para facilitar as coisas, iremos selecionar TODOS os .jars. Em um projeto real, o mais sensato seria selecionar apenas os .jars que você tem certeza que vão ser utilizados. Entretanto, esta abordagem traz um problema. O Jasper utiliza nos bastidores muitos desses .jars, mesmo que a gente pense que não. Um exemplo é o spring.jar :(. E o pior! A cada versão, as dependências mudam (já tive vários problemas com isso). Minha recomendação é que você selecione TODOS os .jars, e depois, caso já utilize um deles (por exemplo o Hibernate) recomendo então que você atualize o .jar para a versão mais nova. Enfim, selecione TODOS e clique em Add JAR/Folder. Veja a Figura abaixo.

Figura 14

Jars do diretório modules/ext

Ao fazer isso, novamente a janela Customize Library vai ser exibida, mostrando que todos os .jars foram inseridos. Clique em OK. Ao clicar, o diálogo Add Library será exibido novamente, mas agora com a nova biblioteca criada. Selecione-a (já vai estar selecionada por padrão) e clique em Add Library. Veja a Figura abaixo.

Figura 15

Adicionando a biblioteca no projeto

Fazendo isso, o diálogo será fechado e você vai perceber que todos os .jars aparecerão dentro do projeto. Veja a Figura abaixo.

Figura 16

Projeto com a biblioteca adicionada

Ok, já temos a biblioteca configurada com todos os .jars do JasperReports. Agora vamos criar um diretório no nosso projeto para conter as definições dos nossos relatórios (arquivos .jrxml). Para isso, clique com o botão direito no nó raiz do projeto e escolha Properties (Propriedades). Veja a Figura abaixo.

Figura 17

Acessando as propriedades do projeto

Ao clicar em Properties, a janela de propriedades do projeto será exibida. Nesta janela, selecione o primeiro nó à esquerda (Source – Fonte) e do lado direito, na tabela Source Package Folders, clique em Add Folder… (Adicionar Diretório). Veja a Figura abaixo.

Figura 18

Propriedades do projeto

Ao clicar em Add Folder, um diálogo será exibido. Neste diálogo, clique no botão localizado no canto superior direito. Veja a Figura abaixo.

Figura 19

Botão para criar um novo diretório

Com isso, um novo diretório será criado. Dê o nome de “relatorios” (sem as aspas e sem acento agudo), selecione-o e clique em Open. Veja a Figura abaixo.

Figura 20

Diretório "relatorios" criado

Ao clicar em Open, o novo diretório será inserido na tabela Source Package Folders. Na primeira coluna é apresentado o caminho do diretório, enquanto na segunda coluna é apresentado o label que vai identificar esse diretório dentro do projeto. Clique duas vezes na célula da segunda coluna que corresponde ao label do diretório criado e dê o nome de “Relatórios”. Cuidado! Ao terminar de editar o nome, tecle <ENTER>. Veja a Figura abaixo.

Figura 21

Configurando o novo diretório

Ao fazer isso, o label será configurado (saindo da edição) e você agora deve clicar no botão OK das propriedades do projeto, aceitando assim as modificações que foram feitas. Veja a Figura abaixo.

Figura 22

Aceitando as configurações

Pronto, terminamos a configuração do projeto. Se tudo tiver dado certo, o novo diretório será exibido na estrutura do projeto, sendo que ele será identificado pelo label que foi configurado no passo anterior. Veja a Figura abaixo.

Figura 23

Estrutura do projeto atualizada

Com isso terminamos a primeira parte do tutorial. Aprendemos alguns conceitos sobre o JasperReports e como instalar o iReport. Aprendemos também a criar e a inserir uma nova biblioteca no projeto, sendo que essa biblioteca corresponde aos .jars necessários para nós utilizarmos o JasperReports no nosso programa (quando formos abrir o relatório a partir do programa). Além disso, aprendemos a criar um diretório dedicado aos nossos relatórios. Então é isso. Terminamos a primeira parte do tutorial. Com o cenário pronto, agora podemos começar a brincar com o iReport e criar nossos primeiros relatórios.

O projeto completo até o momento pode ser obtido neste link.

Fiquem ligados, a próxima parte vem ai 😉

Parte 1Parte 2Parte 3Parte 4Parte 5


Olá a todos. Estive um pouco sumido nos últimos dias. Criar esses tutoriais detalhados demanda um trabalho considerável sendo preciso ter coragem (e tempo) para fazer hehe. O Sérgio, leitor do Blog, me pediu algumas dicas em como estruturar uma aplicação feita no Flex em janelas. Pois bem, decidi então fazer um tutorial sobre isso. Para o tutorial, iremos utilizar como base o tutorial anterior e quem ainda não o leu, pode iniciar pela Parte 1.

Vamos começar então. Primeiro, vamos fazer uma cópia do nosso projeto do frontend. Para isso, no Flash Builder, procure pelo projeto “IntegracaoFlexJavaGUI”, clique com o botão direito do mouse na raiz do projeto e clique em “Copy”. Veja a Figura abaixo.

 

Figura 1

Copiando o projeto

 

Com o projeto copiado, agora basta colar. Para isso, clique em algum lugar fora do projeto original e escolha “Paste”. Veja a Figura abaixo.

 

Figura 2

Colando o projeto

 

Quando você clicar na opção “Paste”, a janela “Copy Project” vai aparecer. No campo “Project Name” vamos preencher com o nome “IntegracaoFlexJavaGUIComJanelas”. Desmarque a opção “Use default location” e no campo “Location” localize a pasta onde estamos salvando nossos projetos. No meu caso, estou salvando tudo na pasta “C:\Users\David\Documents\Java\Flex”. Tome o cuidado de indicar que você quer que uma pasta para o projeto seja criada, colocando no final do caminho o nome do projeto. No meu caso, o valor completo do campo “Location” ficou “C:\Users\David\Documents\Java\Flex\IntegracaoFlexJavaGUIComJanelas” (sem as aspas). Com isso pronto, clique no OK. Veja a Figura abaixo.

 

Figura 3

Criando o novo projeto com base no anterior

 

Legal, o Flash Builder vai criar então o novo projeto. Perceba que não vamos precisar configurar o caminho do backend, pois ele já estava configurado no projeto anterior, e como fizemos uma cópia, todas aquelas propriedades que alteramos quando criamos o projeto original foram copiadas.

Agora vamos reorganizar nosso novo projeto. Cuidado para não fazer as modificações no projeto antigo dentro do Flash Builder. Clique então com o botão direito na pasta “src”, escolha New > Package. Dê o nome de “gui” (sem as aspas) para o pacote e clique em Finish. Com isso, criamos um novo pacote para o nosso projeto, onde vamos colocar nossos arquivos mxml que vão representar nossa interface gráfica e seus componentes.

Perceba que nossos mxmls ficaram todos no “default package”. Primeiramente selecione o arquivo IntegracaoFlexJavaGUI.mxml e remova-o do projeto (selecione o arquivo, tecle <DELETE>). Sobraram dois arquivos, o CRUD.mxml e o DateRenderer.mxml. Selecione os dois arquivos e os arraste para o pacote “gui” que acabamos de criar. A janela “Move” vai aparecer. Deixe apenas a opção “Update references” marcada e clique em OK. Com isso os arquivos serão movidos para o novo pacote.

Feito isso, clique agora com o botão direito no pacote “gui” e escolha New > MXML Application. Preencha o campo “Name” com “Principal” (sem aspas) e deixe o campo “Layout” como “None”. Clique em Finish. O Flash Builder não vai criar o nosso arquivo no pacote “gui”, mesmo tendo selecionado esse pacote quando clicamos com o botão direito :(. O Principal.mxml vai ser criado no default package. Da mesma forma que acabamos de fazer com os arquivos CRUD.mxml e DateRenderer.mxml, selecione o arquivo Principal.mxml e o mova para o pacote “gui”.

Perceba que o ícone do CRUD.mxml tem uma bolinha azul. Isso quer dizer que esse arquivo é o ponto de entrada da nossa aplicação, ou seja, é a partir dele que a aplicação inicia. Vamos mudar isso. Queremos que o Principal.mxml seja o ponto de entrada. Para isso, clique com o botão direito no Principal.mxml e escolha a opção “Set as Default Application”. Você vai ver que a bolinha vai passar para o Principal.mxml. Iremos manter o CRUD.mxml, pois é nele que está implementada a nossa aplicação original e vamos reaproveitar aquele código quando estivermos montando nossas janelas.

Antes de qualquer outra coisa, lembra que o resultado da compilação do Flash Builder era armazenado na pasta “swf” do nosso projeto do NetBeans? Pois bem, vá no NetBeans, procure a pasta “swf” e apague todo o seu conteúdo. Lá dentro você vai ver que tem arquivos do nosso frontend antigo que não nos interessam mais. Faltou falar uma coisa. Iremos reaproveitar o projeto do NetBeans, então não iremos criar um projeto novo. Caso você queira fazer isso, o processo é parecido com o do Flash Builder. Só não fiz isso, pois senão o caminho do projeto do backend iria mudar e então teríamos que configurar mais coisas no nosso projeto no Flash Builder, mudando o caminho do backend. Sendo assim, sugiro que mantenha o projeto antigo do NetBeans.

Ainda no projeto do NetBeans, abra o index.jsp e agora no <meta … > mude a url que define o redirecionamento para o CRUD.html, colocando no lugar o Principal.html (que é o arquivo HTML que vai ser gerado para conter nosso projeto). O <meta … > do index.jsp vai ficar assim:

Alteração da tag <meta … >

<meta http-equiv="Refresh" content="0; url=swf/Principal.html">

Rode o projeto do NetBeans. Uma página em branco deve aparecer, afinal, ainda não colocamos nada no nosso Principal.mxml. Não precisaremos mais mexer no NetBeans, só iremos utilizá-lo agora para executar nosso projeto quando formos fazendo alterações na interface gráfica. Lembre-se, nossa infraestrutura de persistência e de serviços já está pronta!

Vamos então à montagem da nossa interface gráfica. Para isso, no Flash Builder, abra o Principal.mxml e vá na aba Design. Na aba dos componentes (canto inferior esquerdo) procure pelo grupo “Navigators”. Dento desse grupo existe o componente “MenuBar”. Clique e arraste um MenuBar para o Principal.mxml. A aparência da barra de menu, por enquanto, é igual a de um botão, mas sem nenhum texto.Veja a Figura abaixo.

 

Figura 4

Arrastando um componente MenuBar

 

Queremos então que esse menu ocupe toda a parte acima da nossa aplicação. Para isso, arraste o componente até o canto superior esquerdo do Principal.mxml. Veja a Figura abaixo.

 

Figura 5

Posicionando o componente MenuBar

 

Feito isso, selecione o componente (clique nele) caso ainda não esteja selecionado. No canto inferior direito do Flash Buillder, na aba “Properties” podemos alterar as propriedades do componente. Iremos utilizar a visualização padrão das propriedades (Standard View), que nos fornece um editor amigável para as propriedades mais comuns do componente selecionado no momento. Veja a Figura abaixo. O botão para acessar as propriedades padrão está destacado.

 

Figura 6

Propriedades padrão do componente selecionado

 

No painel de propriedades, com o MeuBar selecionado, procure pela seção “Size and Position”. X e Y devem estar com 0, pois colocamos nosso componente no canto superior (Y=0) esquerdo (X=0) lembram? Agora vamos usar duas constrains para esticar a barra de menu, fazendo ela ocupar toda a interface horizontalmente. Veja a Figura abaixo.

 

Figura 7

Usando as constrains left e right para esticar o componente MenuBar

 

Veja que quando você modificar essas propriedades, a barra de menu vai passar a ocupar toda a interface na horizontal. Note também que com isso, a propriedade X ficou sem um valor. Isso porque quando especificamos o left (checkbox à esquerda) como 0 e o right (checkbox à direita) como 0 também (campos abaixo) não precisamos mais dizer onde o componente está na posição X, pois ele vai preencher horizontalmente seu container, no casso, o Principal.mxml (que é do tipo s:Application).

Vamos agora inserir os itens no nossa barra de menu. Infelizmente não temos uma opção visual para isso, então temos que fazer isso diretamente no código. Para isso, vá na opção “Source” do Principal.mxml e localize o seu MenuBar (<mx:MenuBar>).

Altere o código para isso aqui:

Código do componente MenuBar

<mx:MenuBar y="0" left="0" right="0" labelField="@label">
	<fx:XMLList>
		<menuitem label="Cadastros">
			<menuitem id="itemCadClientes" label="Clientes"/>
			<menuitem type="separator"/>
			<menuitem id="itemCadCidades" label="Cidades"/>
			<menuitem id="itemCadEstados" label="Estados"/>
		</menuitem>
	</fx:XMLList>
</mx:MenuBar>

Parece complicado, mas não é. Vamos primeiro detalhar o conteúdo da tag <mx:Menu>. Perceba que dentro do nosso menu, declaramos uma estrutura de dados chamada XMLList. Basicamente essa estrutura utiliza código XML para estruturar dados. Note que dentro das tags <fx:XMLList> e </fx:XMLList> temos o código XML que será utilizado para estruturar o nosso menu. Temos então um item para o cadastro, representado pela tag <menuitem> mais externa. Note que essa tag tem uma propriedade chamada “label”. Essa propriedade será utilizada pelo renderizador da barra de menus para desenhar o nome de cada botão. Note na tag <mx:MenuBar> o uso da propriedade “labelField”, onde usamos o valor “@label”. Isso significa que queremos usar o atributo “label” das tags definidas no XML como o label (rótulo) dos nossos itens do menu. O sinal de arroba (@) indica que “label” é um atributo. Dentro então da tag <menuitem> mais externa, temos então 4 outros itens, um para cada cadastro e um para o separador do menu (type=”separator”). Note que podemos ir aninhando tags do tipo <menuitem> para irmos construindo nosso menu.

Para ver as mudanças, dê o build no projeto (botão direito na raiz do projeto > Build Project) e execute a aplicação pelo NetBeans. Legal, o menu funciona. Agora falta o principal, ou seja, adicionar interação nos itens do menu, pois queremos que algo aconteça quando um dos itens do cadastro sejam clicados. Para isso iremos registrar um ouvinte na barra de menu, e na implementação do método ouvinte, verificamos qual foi o item clicado com base no id do item. Segue então o código completo do Principal.mxml.

gui.Principal.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">

	<fx:Script>
		<![CDATA[
			import mx.controls.Alert;
			import mx.events.MenuEvent;

			private function menuItemClickHandler( event: MenuEvent ): void {

				// faz o switch baseado no id do item selecionado
				switch ( event.item.@id.toString() ) {

					case "itemCadClientes":
						Alert.show( "Botão Clientes clicado!", "Mensagem" );
						break;

					case "itemCadCidades":
						Alert.show( "Botão Cidades clicado!", "Mensagem" );
						break;

					case "itemCadEstados":
						Alert.show( "Botão Estados clicado!", "Mensagem" );
						break;

				}

			}

		]]>
	</fx:Script>

	<fx:Declarations>
		<!-- Place non-visual  elements (e.g., services, value objects) here -->
	</fx:Declarations>

	<mx:MenuBar y="0" left="0" right="0" labelField="@label" itemClick="menuItemClickHandler(event)">
		<fx:XMLList>
			<menuitem label="Cadastros">
				<menuitem id="itemCadClientes" label="Clientes"/>
				<menuitem type="separator"/>
				<menuitem id="itemCadCidades" label="Cidades"/>
				<menuitem id="itemCadEstados" label="Estados"/>
			</menuitem>
		</fx:XMLList>
	</mx:MenuBar>

</s:Application>

Teste o projeto. Verifique que agora, baseado no item clicado no menu, um Alert diferente é mostrado. Vamos agora implementar nossa primeira janela. Quando tivermos ela pronta, iremos chamá-la pelo botão correspondente. Iniciaremos pela janela de cadastro de Estados.

Para isso, vamos criar um componente mxml, mas antes um pouco de teoria. Quando criamos arquivos MXML, na verdade estamos estendendo classes do ActionScript. Quando criamos uma aplicação MXML (MXML Application), você pode notar que o arquivo fonte é escrito em XML e que a tag raiz (root) é do tipo <s:Application>. Durante a compilação, o que o Flex faz é converter esse arquivo MXML em uma classe e então o compila. Sendo assim, cada arquivo MXML vira uma classe e no final e estamos estendendo uma classe. Imagine então o nosso arquivo Principal.mxml. Na verdade, ele vira uma classe mais ou menos assim:

package gui {

	import spark.components.Application;

	public class Principal extends Application {

		// implementação...

	}

}

Para cada uma das nossas janelas, nós vamos estender a classe “TitleWindow” do pacote spark.components, mas nós vamos seguir a estratégia de criar arquivos MXML da mesma forma que estamos fazendo para as aplicações. Então vamos lá. Começaremos pela tela de cadastro de Estados. Clique com o botão direito no pacote “gui” e escolha New >MXML Component. Na tela New MXML Component, preencha o campo “Name” com “CadastroEstados” (sem as aspas), deixe o campo “Layout” como “None” e por fim, preencha o “Based on” com “spark.components.TitleWindow” (semas as aspas). Por fim, clique em Finish e arquivo do novo componente será aberto. Deixa e largura e a altura da forma que estão. Veja a Figura abaixo.

 

Figura 8

Criando um novo componente para o cadastro de Estados

 

Com o arquivo CadastroEstados.mxml aberto, vá na aba Design e vamos fazer as primeiras modificações na nossa tela de cadastro. Primeiro dê um título para a janela (pelas propriedades ou clicando duas vezes na barra de título). Sugiro “Cadastro de Estados” (sem as aspas). Com isso feito, aumente um pouco a tela para termos espaço para trabalhar. Basta redimensionar, como faria com uma imagem. No final, teremos algo assim:

 

Figura 9

Preparando a janela de cadastro

 

Agora vamos ao componentes da tela de cadastro. O bom é que já temos tudo pronto :). Abra o arquivo CRUD.mxml, vá em Design, selecione todos os componentes do painel de cadastro de estados, clique bom o botão direito e escolha “Copy”. Veja a Figura.

 

Figura 10

Copiando componentes

 

Agora volte no CadastroEstados.mxml, e em Design, clique com o botão direito na tela e escolha “Paste”. Os componentes serão colados. Organize eles e redimensione a janela. O resultado você pode ver na Figura abaixo, e o código parcial do CadastroEstados.mxml pode ser visto logo em seguinda.

 

Figura 11

Design da tela de cadastro de Estados pronto

 

gui.CadastroEstados.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:TitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx" width="296" height="278" title="Cadastro de Estados">

	<fx:Declarations>
		<!-- Place non-visual elements (e.g., services, value objects) here -->
	</fx:Declarations>

	<mx:DataGrid y="10" id="tabelaEstados" left="10" right="10" height="107" itemClick="tabelaEstadosClickHandler(event)">
		<mx:columns>
			<mx:DataGridColumn headerText="Nome" dataField="nome" width="150" resizable="true"/>
			<mx:DataGridColumn headerText="Sigla" dataField="sigla" width="80" resizable="true"/>
		</mx:columns>
	</mx:DataGrid>
	<mx:Form y="121" right="10" left="10" height="86">
		<mx:FormItem label="Nome:">
			<s:TextInput width="187" id="fieldNomeEstado"/>
		</mx:FormItem>
		<mx:FormItem label="Sigla:">
			<s:TextInput width="40" id="fieldSiglaEstado"/>
		</mx:FormItem>
	</mx:Form>
	<s:Button y="215" label="Novo" click="btnNovoEstadoClickHandler(event)" right="162"/>
	<s:Button y="215" label="Salvar" click="btnSalvarEstadoClickHandler(event)" right="84"/>
	<s:Button y="215" label="Excluir" click="btnExcluirEstadoClickHandler(event)" right="6"/>
</s:TitleWindow>

Além da parte gráfica, precisamos agora copiar o código dos manipuladores de eventos, dos serviços, etc. Como você já tem alguma experiência com o funcionamento, pois provavelmente já acompanhou o tutorial anterior, estou postando o código completo do CasdatroEstados.mxml com todo o código da interação implementado.

gui.CadastroEstados.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:TitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx" width="296" height="278" title="Cadastro de Estados"
			   creationComplete="creationCompleteHandler(event)">

	<fx:Script>
		<![CDATA[
			import entidades.Estado;

			import mx.collections.ArrayCollection;
			import mx.controls.Alert;
			import mx.events.FlexEvent;
			import mx.events.ItemClickEvent;
			import mx.events.ListEvent;
			import mx.rpc.events.FaultEvent;
			import mx.rpc.events.ResultEvent;

			private var estadoSelec: Estado;

			private function faultHandler( event: FaultEvent ): void {
				Alert.show( event.fault.toString(), "ERRO" );
			}

			private function estadoSaveResultHandler( event: ResultEvent ): void {
				servEstado.listAll();
				resetFormEstados();
			}

			private function estadoUpdateResultHandler( event: ResultEvent ): void {
				servEstado.listAll();
				resetFormEstados();
			}

			private function estadoDeleteResultHandler( event: ResultEvent ): void {
				servEstado.listAll();
				resetFormEstados();
			}

			private function estadoListAllResultHandler( event: ResultEvent ): void {
				tabelaEstados.dataProvider = event.result;
			}

			private function btnNovoEstadoClickHandler( event: MouseEvent ): void {
				resetFormEstados();
			}

			private function btnSalvarEstadoClickHandler( event: MouseEvent ): void {

				if ( estadoSelec ) {

					estadoSelec.nome = fieldNomeEstado.text;
					estadoSelec.sigla = fieldSiglaEstado.text;

					servEstado.update(estadoSelec);

				} else {

					var o: Estado = new Estado();

					o.nome = fieldNomeEstado.text;
					o.sigla = fieldSiglaEstado.text;

					servEstado.save(o);

				}
			}

			private function btnExcluirEstadoClickHandler( event: MouseEvent ): void {

				if ( estadoSelec ) {
					servEstado.getOperation( "delete" ).send( estadoSelec );
				}

			}

			private function tabelaEstadosClickHandler( event: ListEvent ): void {

				estadoSelec = tabelaEstados.selectedItem as Estado;

				fieldNomeEstado.text = estadoSelec.nome;
				fieldSiglaEstado.text = estadoSelec.sigla;

			}

			private function resetFormEstados(): void {

				fieldNomeEstado.text = "";
				fieldSiglaEstado.text = "";

				estadoSelec = null;

			}

			private function creationCompleteHandler( event: FlexEvent ): void {
				servEstado.listAll();
			}

		]]>
	</fx:Script>

	<fx:Declarations>

		<s:RemoteObject
			id="servEstado"
			destination="estadoServices"
			showBusyCursor="true">

			<s:method
				name="save"
				fault="faultHandler(event)"
				result="estadoSaveResultHandler(event)"/>

			<s:method
				name="update"
				fault="faultHandler(event)"
				result="estadoUpdateResultHandler(event)"/>

			<s:method
				name="delete"
				fault="faultHandler(event)"
				result="estadoDeleteResultHandler(event)"/>

			<s:method
				name="listAll"
				fault="faultHandler(event)"
				result="estadoListAllResultHandler(event)"/>

		</s:RemoteObject>

	</fx:Declarations>

	<mx:DataGrid y="10" id="tabelaEstados" left="10" right="10" height="107" itemClick="tabelaEstadosClickHandler(event)">
		<mx:columns>
			<mx:DataGridColumn headerText="Nome" dataField="nome" width="150" resizable="true"/>
			<mx:DataGridColumn headerText="Sigla" dataField="sigla" width="80" resizable="true"/>
		</mx:columns>
	</mx:DataGrid>
	<mx:Form y="121" right="10" left="10" height="86">
		<mx:FormItem label="Nome:">
			<s:TextInput width="187" id="fieldNomeEstado"/>
		</mx:FormItem>
		<mx:FormItem label="Sigla:">
			<s:TextInput width="40" id="fieldSiglaEstado"/>
		</mx:FormItem>
	</mx:Form>
	<s:Button y="215" label="Novo" click="btnNovoEstadoClickHandler(event)" right="162"/>
	<s:Button y="215" label="Salvar" click="btnSalvarEstadoClickHandler(event)" right="84"/>
	<s:Button y="215" label="Excluir" click="btnExcluirEstadoClickHandler(event)" right="6"/>
</s:TitleWindow>

Legal,  já temos nossa tela de cadastro pronta (quase na verdade). Vamos então aprender como fazer para abrir essa janela de cadastro a partir do menu do Principal.mxml. Usaremos para isso a classe PopUpManager do pacote mx.managers. Essa classe possui dois métodos estáticos, o addPopUp e o removePopUp. Esses métodos são usados respectivamente para adicionar uma janela em um container e para remover uma janela de um conteiner. Vamos então modificar o switch do Principal.mxml onde tratamos os eventos dos itens dos botões. Segue então o código do Principal.mxml atualizado e comentado.

gui.Principal.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">

	<fx:Script>
		<![CDATA[
			import mx.controls.Alert;
			import mx.events.MenuEvent;
			import mx.managers.PopUpManager;

			private function menuItemClickHandler( event: MenuEvent ): void {

				// faz o switch baseado no id do item selecionado
				switch ( event.item.@id.toString() ) {

					case "itemCadClientes":
						Alert.show( "Botão Clientes clicado!", "Mensagem" );
						break;

					case "itemCadCidades":
						Alert.show( "Botão Cidades clicado!", "Mensagem" );
						break;

					case "itemCadEstados":

						// instanciamos um objeto do tipo CadastroEstados
						var cadastroEstados: CadastroEstados = new CadastroEstados();

						/*
						Usa-se a classe PopUpManager para abrir a janela.
						O primeiro parâmetro é o componente a ser aberto, o segundo é
						onde ele estará contido e o terceiro é um boolean que diz se
						a janela deve ser modal ou não.
						*/
						PopUpManager.addPopUp( cadastroEstados, this, true );

						// centralizamos a janela
						cadastroEstados.x = ( this.width / 2 ) - ( cadastroEstados.width / 2 );
						cadastroEstados.y = ( this.height / 2 ) - ( cadastroEstados.height / 2 );

						break;

				}

			}

		]]>
	</fx:Script>

	<fx:Declarations>
		<!-- Place non-visual  elements (e.g., services, value objects) here -->
	</fx:Declarations>

	<mx:MenuBar y="0" left="0" right="0" labelField="@label" itemClick="menuItemClickHandler(event)">
		<fx:XMLList>
			<menuitem label="Cadastros">
				<menuitem id="itemCadClientes" label="Clientes"/>
				<menuitem type="separator"/>
				<menuitem id="itemCadCidades" label="Cidades"/>
				<menuitem id="itemCadEstados" label="Estados"/>
			</menuitem>
		</fx:XMLList>
	</mx:MenuBar>

</s:Application>

Teste sua aplicação. Você vai perceber que agora, quando clicar no item Estados do menu Cadastros, a janela de cadastro de Estados vai abrir. Note que ela é modal (um overlay vai esmaecer a tela principal) e que você pode arrastá-la como uma janela. O único detalhe que ainda falta é permitir que ela seja fechada. Se você clicar no botão fechar da janela, não acontecerá nada. Para implementar isso, vamos definir um método para manipular o evento “close” da janela de cadastro de Estados. Esse evento é disparado quando clicamos no botão fechar. O código do método é super simples. Ele vai chamar o método removePopUp da classe PopUpManager, passando como referência o próprio componente. Segue então o código final da janela de cadastro de Estados.

gui.CadastroEstados.mxml (final)

<?xml version="1.0" encoding="utf-8"?>
<s:TitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx" width="296" height="278" title="Cadastro de Estados"
			   creationComplete="creationCompleteHandler(event)"
			   close="closeHandler(event)">

	<fx:Script>
		<![CDATA[
			import entidades.Estado;

			import mx.collections.ArrayCollection;
			import mx.controls.Alert;
			import mx.events.CloseEvent;
			import mx.events.FlexEvent;
			import mx.events.ItemClickEvent;
			import mx.events.ListEvent;
			import mx.managers.PopUpManager;
			import mx.rpc.events.FaultEvent;
			import mx.rpc.events.ResultEvent;

			private var estadoSelec: Estado;

			private function faultHandler( event: FaultEvent ): void {
				Alert.show( event.fault.toString(), "ERRO" );
			}

			private function estadoSaveResultHandler( event: ResultEvent ): void {
				servEstado.listAll();
				resetFormEstados();
			}

			private function estadoUpdateResultHandler( event: ResultEvent ): void {
				servEstado.listAll();
				resetFormEstados();
			}

			private function estadoDeleteResultHandler( event: ResultEvent ): void {
				servEstado.listAll();
				resetFormEstados();
			}

			private function estadoListAllResultHandler( event: ResultEvent ): void {
				tabelaEstados.dataProvider = event.result;
			}

			private function btnNovoEstadoClickHandler( event: MouseEvent ): void {
				resetFormEstados();
			}

			private function btnSalvarEstadoClickHandler( event: MouseEvent ): void {

				if ( estadoSelec ) {

					estadoSelec.nome = fieldNomeEstado.text;
					estadoSelec.sigla = fieldSiglaEstado.text;

					servEstado.update(estadoSelec);

				} else {

					var o: Estado = new Estado();

					o.nome = fieldNomeEstado.text;
					o.sigla = fieldSiglaEstado.text;

					servEstado.save(o);

				}
			}

			private function btnExcluirEstadoClickHandler( event: MouseEvent ): void {

				if ( estadoSelec ) {
					servEstado.getOperation( "delete" ).send( estadoSelec );
				}

			}

			private function tabelaEstadosClickHandler( event: ListEvent ): void {

				estadoSelec = tabelaEstados.selectedItem as Estado;

				fieldNomeEstado.text = estadoSelec.nome;
				fieldSiglaEstado.text = estadoSelec.sigla;

			}

			private function resetFormEstados(): void {

				fieldNomeEstado.text = "";
				fieldSiglaEstado.text = "";

				estadoSelec = null;

			}

			private function creationCompleteHandler( event: FlexEvent ): void {
				servEstado.listAll();
			}

			private function closeHandler( event: CloseEvent ): void {
				PopUpManager.removePopUp(this);
			}

		]]>
	</fx:Script>

	<fx:Declarations>

		<s:RemoteObject
			id="servEstado"
			destination="estadoServices"
			showBusyCursor="true">

			<s:method
				name="save"
				fault="faultHandler(event)"
				result="estadoSaveResultHandler(event)"/>

			<s:method
				name="update"
				fault="faultHandler(event)"
				result="estadoUpdateResultHandler(event)"/>

			<s:method
				name="delete"
				fault="faultHandler(event)"
				result="estadoDeleteResultHandler(event)"/>

			<s:method
				name="listAll"
				fault="faultHandler(event)"
				result="estadoListAllResultHandler(event)"/>

		</s:RemoteObject>

	</fx:Declarations>

	<mx:DataGrid y="10" id="tabelaEstados" left="10" right="10" height="107" itemClick="tabelaEstadosClickHandler(event)">
		<mx:columns>
			<mx:DataGridColumn headerText="Nome" dataField="nome" width="150" resizable="true"/>
			<mx:DataGridColumn headerText="Sigla" dataField="sigla" width="80" resizable="true"/>
		</mx:columns>
	</mx:DataGrid>
	<mx:Form y="121" right="10" left="10" height="86">
		<mx:FormItem label="Nome:">
			<s:TextInput width="187" id="fieldNomeEstado"/>
		</mx:FormItem>
		<mx:FormItem label="Sigla:">
			<s:TextInput width="40" id="fieldSiglaEstado"/>
		</mx:FormItem>
	</mx:Form>
	<s:Button y="215" label="Novo" click="btnNovoEstadoClickHandler(event)" right="162"/>
	<s:Button y="215" label="Salvar" click="btnSalvarEstadoClickHandler(event)" right="84"/>
	<s:Button y="215" label="Excluir" click="btnExcluirEstadoClickHandler(event)" right="6"/>
</s:TitleWindow>

Teste e perceba que agora a janela é fechada, e o foco retorna para a tela principal da aplicação. Muito bem. Agora como exercício, faça o mesmo processo para as outras duas telas. Copie os componentes, organize-os, copie o código dos manipuladores de eventos, dos serviços, remova algum código que esteja sobrando e altere o Principal.mxml para abrir as janelas restantes. Note que para as janelas de cadastro de Cidades e de Clientes você precisará de outros serviços além do da entidade tratada na janela. Por exemplo, no cadastro de Cidades, você precisará do serviço de estados também (o combo de estados, lembra?). Não se esqueça do evento close das janelas. Para finalizar, seguem os códigos finais das janelas de cadastro de Cidades e de Clientes o código final do Principal.mxml. Agora você pode apagar o CRUD.mxml, pois já separamos todo o seu código nas nossas telas.

gui.CadastroCidades.mxml (final)

<?xml version="1.0" encoding="utf-8"?>
<s:TitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx" width="304" height="272" title="Cadastro de Cidades"
			   creationComplete="creationCompleteHandler(event)"
			   close="closeHandler(event)">

	<fx:Script>
		<![CDATA[
			import entidades.Cidade;
			import entidades.Estado;

			import mx.collections.ArrayCollection;
			import mx.controls.Alert;
			import mx.events.CloseEvent;
			import mx.events.FlexEvent;
			import mx.events.ItemClickEvent;
			import mx.events.ListEvent;
			import mx.managers.PopUpManager;
			import mx.rpc.events.FaultEvent;
			import mx.rpc.events.ResultEvent;

			private var cidadeSelec: Cidade;

			private function faultHandler( event: FaultEvent ): void {
				Alert.show( event.fault.toString(), "ERRO" );
			}

			private function cidadeSaveResultHandler( event: ResultEvent ): void {
				servCidade.listAll();
				resetFormCidades();
			}

			private function cidadeUpdateResultHandler( event: ResultEvent ): void {
				servCidade.listAll();
				resetFormCidades();
			}

			private function cidadeDeleteResultHandler( event: ResultEvent ): void {
				servCidade.listAll();
				resetFormCidades();
			}

			private function cidadeListAllResultHandler( event: ResultEvent ): void {
				tabelaCidades.dataProvider = event.result;
			}

			private function estadoListAllResultHandler( event: ResultEvent ): void {
				comboEstadoCidade.dataProvider = event.result as ArrayCollection;
			}

			private function btnNovoCidadeClickHandler( event: MouseEvent ): void {
				resetFormCidades();
			}

			private function btnSalvarCidadeClickHandler( event: MouseEvent ): void {

				if ( cidadeSelec ) {

					cidadeSelec.nome = fieldNomeCidade.text;
					cidadeSelec.estado = comboEstadoCidade.selectedItem as Estado;

					servCidade.update(cidadeSelec);

				} else {

					var o: Cidade = new Cidade();

					o.nome = fieldNomeCidade.text;
					o.estado = comboEstadoCidade.selectedItem as Estado;

					servCidade.save(o);

				}

			}

			private function btnExcluirCidadeClickHandler( event: MouseEvent ): void {
				if ( cidadeSelec ) {
					servCidade.getOperation( "delete" ).send( cidadeSelec );
				}
			}

			private function tabelaCidadesClickHandler( event: ListEvent ): void {

				cidadeSelec = tabelaCidades.selectedItem as Cidade;

				fieldNomeCidade.text = cidadeSelec.nome;
				comboEstadoCidade.selectedItem = cidadeSelec.estado;

			}

			private function resetFormCidades(): void {

				fieldNomeCidade.text = "";
				comboEstadoCidade.selectedIndex = -1;

				cidadeSelec = null;

			}

			private function creationCompleteHandler( event: FlexEvent ): void {
				servCidade.listAll();
				servEstado.listAll();
			}

			private function closeHandler( event: CloseEvent ): void {
				PopUpManager.removePopUp(this);
			}

		]]>
	</fx:Script>

	<fx:Declarations>

		<s:RemoteObject
			id="servCidade"
			destination="cidadeServices"
			showBusyCursor="true">

			<s:method
				name="save"
				fault="faultHandler(event)"
				result="cidadeSaveResultHandler(event)"/>

			<s:method
				name="update"
				fault="faultHandler(event)"
				result="cidadeUpdateResultHandler(event)"/>

			<s:method
				name="delete"
				fault="faultHandler(event)"
				result="cidadeDeleteResultHandler(event)"/>

			<s:method
				name="listAll"
				fault="faultHandler(event)"
				result="cidadeListAllResultHandler(event)"/>

		</s:RemoteObject>

		<s:RemoteObject
			id="servEstado"
			destination="estadoServices"
			showBusyCursor="true">

			<s:method
				name="listAll"
				fault="faultHandler(event)"
				result="estadoListAllResultHandler(event)"/>

		</s:RemoteObject>

	</fx:Declarations>

	<mx:DataGrid y="10" id="tabelaCidades" height="106" itemClick="tabelaCidadesClickHandler(event)" left="10" right="10">
		<mx:columns>
			<mx:DataGridColumn headerText="Nome" dataField="nome" width="180"/>
			<mx:DataGridColumn headerText="Estado" dataField="estado.nome" width="150"/>
		</mx:columns>
	</mx:DataGrid>
	<mx:Form y="120" left="10" right="10" height="83">
		<mx:FormItem label="Nome:">
			<s:TextInput id="fieldNomeCidade" width="189"/>
		</mx:FormItem>
		<mx:FormItem label="Estado:">
			<s:ComboBox id="comboEstadoCidade"/>
		</mx:FormItem>
	</mx:Form>
	<s:Button y="211" label="Novo" click="btnNovoCidadeClickHandler(event)" right="166"/>
	<s:Button y="211" label="Salvar" click="btnSalvarCidadeClickHandler(event)" right="88"/>
	<s:Button y="211" label="Excluir" click="btnExcluirCidadeClickHandler(event)" right="10"/>
</s:TitleWindow>

gui.CadastroClientes.mxml (final)

<?xml version="1.0" encoding="utf-8"?>
<s:TitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx" width="484" height="562" title="Cadastro de Clientes"
			   creationComplete="creationCompleteHandler(event)"
			   close="closeHandler(event)">

	<fx:Script>
		<![CDATA[
			import entidades.Cidade;
			import entidades.Cliente;

			import mx.collections.ArrayCollection;
			import mx.controls.Alert;
			import mx.events.CloseEvent;
			import mx.events.FlexEvent;
			import mx.events.ItemClickEvent;
			import mx.events.ListEvent;
			import mx.managers.PopUpManager;
			import mx.rpc.events.FaultEvent;
			import mx.rpc.events.ResultEvent;

			private var clienteSelec: Cliente;

			private function faultHandler( event: FaultEvent ): void {
				Alert.show( event.fault.toString(), "ERRO" );
			}

			private function cidadeListAllResultHandler( event: ResultEvent ): void {
				comboCidadeCliente.dataProvider = event.result as ArrayCollection;
			}

			private function clienteSaveResultHandler( event: ResultEvent ): void {
				servCliente.listAll();
				resetFormClientes();
			}

			private function clienteUpdateResultHandler( event: ResultEvent ): void {
				servCliente.listAll();
				resetFormClientes();
			}

			private function clienteDeleteResultHandler( event: ResultEvent ): void {
				servCliente.listAll();
				resetFormClientes();
			}

			private function clienteListAllResultHandler( event: ResultEvent ): void {
				tabelaClientes.dataProvider = event.result;
			}

			private function btnNovoClienteClickHandler( event: MouseEvent ): void {
				resetFormClientes();
			}

			private function btnSalvarClienteClickHandler( event: MouseEvent ): void {

				if ( clienteSelec ) {

					clienteSelec.nome = fieldNomeCliente.text;
					clienteSelec.sobrenome = fieldSobrenomeCliente.text;
					clienteSelec.cpf = fieldCPFCliente.text;
					clienteSelec.dataNascimento = fieldDataNascCliente.selectedDate;
					clienteSelec.rua = fieldRuaCliente.text;
					clienteSelec.numero = fieldNumeroCliente.text;
					clienteSelec.cep = fieldCEPCliente.text;
					clienteSelec.cidade = comboCidadeCliente.selectedItem as Cidade;

					servCliente.update(clienteSelec);

				} else {

					var o: Cliente = new Cliente();

					o.nome = fieldNomeCliente.text;
					o.sobrenome = fieldSobrenomeCliente.text;
					o.cpf = fieldCPFCliente.text;
					o.dataNascimento = fieldDataNascCliente.selectedDate;
					o.rua = fieldRuaCliente.text;
					o.numero = fieldNumeroCliente.text;
					o.cep = fieldCEPCliente.text;
					o.cidade = comboCidadeCliente.selectedItem as Cidade;

					servCliente.save(o);

				}

			}

			private function btnExcluirClienteClickHandler( event: MouseEvent ): void {
				if ( clienteSelec ) {
					servCliente.getOperation( "delete" ).send( clienteSelec );
				}
			}

			private function tabelaClientesClickHandler( event: MouseEvent ): void {

				clienteSelec = tabelaClientes.selectedItem as Cliente;

				fieldIdCliente.text = String( clienteSelec.id );
				fieldNomeCliente.text = clienteSelec.nome;
				fieldSobrenomeCliente.text = clienteSelec.sobrenome;
				fieldCPFCliente.text = clienteSelec.cpf;
				fieldDataNascCliente.selectedDate = clienteSelec.dataNascimento;
				fieldRuaCliente.text = clienteSelec.rua;
				fieldNumeroCliente.text = clienteSelec.numero;
				fieldCEPCliente.text = clienteSelec.cep;
				comboCidadeCliente.selectedItem = clienteSelec.cidade;

			}

			private function resetFormClientes(): void {

				fieldIdCliente.text = "";
				fieldNomeCliente.text = "";
				fieldSobrenomeCliente.text = "";
				fieldCPFCliente.text = "";
				fieldDataNascCliente.selectedDate = null;
				fieldRuaCliente.text = "";
				fieldNumeroCliente.text = "";
				fieldCEPCliente.text = "";
				comboCidadeCliente.selectedIndex = -1;

				clienteSelec = null;

			}

			private function creationCompleteHandler( event: FlexEvent ): void {
				servCidade.listAll();
				servCliente.listAll();
			}

			private function closeHandler( event: CloseEvent ): void {
				PopUpManager.removePopUp(this);
			}

		]]>
	</fx:Script>

	<fx:Declarations>

		<s:RemoteObject
			id="servCidade"
			destination="cidadeServices"
			showBusyCursor="true">

			<s:method
				name="listAll"
				fault="faultHandler(event)"
				result="cidadeListAllResultHandler(event)"/>

		</s:RemoteObject>

		<s:RemoteObject
			id="servCliente"
			destination="clienteServices"
			showBusyCursor="true">

			<s:method
				name="save"
				fault="faultHandler(event)"
				result="clienteSaveResultHandler(event)"/>

			<s:method
				name="update"
				fault="faultHandler(event)"
				result="clienteUpdateResultHandler(event)"/>

			<s:method
				name="delete"
				fault="faultHandler(event)"
				result="clienteDeleteResultHandler(event)"/>

			<s:method
				name="listAll"
				fault="faultHandler(event)"
				result="clienteListAllResultHandler(event)"/>

		</s:RemoteObject>

	</fx:Declarations>

	<mx:DataGrid y="6" left="10" right="10" height="196" id="tabelaClientes" click="tabelaClientesClickHandler(event)">
		<mx:columns>
			<mx:DataGridColumn headerText="Nome" dataField="nome"/>
			<mx:DataGridColumn headerText="Sobrenome" dataField="sobrenome"/>
			<mx:DataGridColumn headerText="CPF" dataField="cpf" width="100"/>
			<mx:DataGridColumn dataField="dataNascimento" headerText="Dt. Nasc" width="80" itemRenderer="gui.DateRenderer"/>
			<mx:DataGridColumn dataField="cidade.nome" headerText="Cidade"/>
		</mx:columns>
	</mx:DataGrid>
	<mx:Form y="210" left="10" right="10" height="281">
		<mx:FormItem label="ID:">
			<s:TextInput width="40" enabled="false" id="fieldIdCliente"/>
		</mx:FormItem>
		<mx:FormItem label="Nome:">
			<s:TextInput id="fieldNomeCliente"/>
		</mx:FormItem>
		<mx:FormItem label="Sobrenome:">
			<s:TextInput id="fieldSobrenomeCliente"/>
		</mx:FormItem>
		<mx:FormItem label="CPF:">
			<s:TextInput width="95" id="fieldCPFCliente"/>
		</mx:FormItem>
		<mx:FormItem label="Data de Nascimento:">
			<mx:DateField id="fieldDataNascCliente" formatString="DD/MM/YYYY"/>
		</mx:FormItem>
		<mx:FormItem label="Rua:">
			<s:TextInput id="fieldRuaCliente" width="249"/>
		</mx:FormItem>
		<mx:FormItem label="Número:">
			<s:TextInput width="59" id="fieldNumeroCliente"/>
		</mx:FormItem>
		<mx:FormItem label="CEP:">
			<s:TextInput width="94" id="fieldCEPCliente"/>
		</mx:FormItem>
		<mx:FormItem label="Cidade:">
			<s:ComboBox width="200" id="comboCidadeCliente"/>
		</mx:FormItem>
	</mx:Form>
	<s:Button y="498" label="Novo" click="btnNovoClienteClickHandler(event)" right="166"/>
	<s:Button y="498" label="Salvar" click="btnSalvarClienteClickHandler(event)" right="88"/>
	<s:Button y="498" label="Excluir" right="10" click="btnExcluirClienteClickHandler(event)"/>
</s:TitleWindow>

gui.Principal.mxml (final)

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
			   xmlns:s="library://ns.adobe.com/flex/spark"
			   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">

	<fx:Script>
		<![CDATA[
			import mx.controls.Alert;
			import mx.events.MenuEvent;
			import mx.managers.PopUpManager;

			private function menuItemClickHandler( event: MenuEvent ): void {

				// faz o switch baseado no id do item selecionado
				switch ( event.item.@id.toString() ) {

					case "itemCadClientes":
						var cadastroClientes: CadastroClientes = new CadastroClientes();
						PopUpManager.addPopUp( cadastroClientes, this, true );
						cadastroClientes.x = ( this.width / 2 ) - ( cadastroClientes.width / 2 );
						cadastroClientes.y = ( this.height / 2 ) - ( cadastroClientes.height / 2 );
						break;

					case "itemCadCidades":
						var cadastroCidades: CadastroCidades = new CadastroCidades();
						PopUpManager.addPopUp( cadastroCidades, this, true );
						cadastroCidades.x = ( this.width / 2 ) - ( cadastroCidades.width / 2 );
						cadastroCidades.y = ( this.height / 2 ) - ( cadastroCidades.height / 2 );
						break;

					case "itemCadEstados":

						// instanciamos um objeto do tipo CadastroEstados
						var cadastroEstados: CadastroEstados = new CadastroEstados();

						/*
						Usa-se a classe PopUpManager para abrir a janela.
						O primeiro parâmetro é o componente a ser aberto, o segundo é
						onde ele estará contido e o terceiro é um boolean que diz se
						a janela deve ser modal ou não.
						*/
						PopUpManager.addPopUp( cadastroEstados, this, true );

						// centralizamos a janela
						cadastroEstados.x = ( this.width / 2 ) - ( cadastroEstados.width / 2 );
						cadastroEstados.y = ( this.height / 2 ) - ( cadastroEstados.height / 2 );

						break;

				}

			}

		]]>
	</fx:Script>

	<fx:Declarations>
		<!-- Place non-visual  elements (e.g., services, value objects) here -->
	</fx:Declarations>

	<mx:MenuBar y="0" left="0" right="0" labelField="@label" itemClick="menuItemClickHandler(event)">
		<fx:XMLList>
			<menuitem label="Cadastros">
				<menuitem id="itemCadClientes" label="Clientes"/>
				<menuitem type="separator"/>
				<menuitem id="itemCadCidades" label="Cidades"/>
				<menuitem id="itemCadEstados" label="Estados"/>
			</menuitem>
		</fx:XMLList>
	</mx:MenuBar>

</s:Application>

Com isso terminamos nosso tutorial onde aprendemos a separar nossa aplicação em janelas, fazendo com que ela fique mais modularizada. Como exercício você ainda pode melhorar ainda mais cada cadastro, adicionando validadores aos formulários, criando um método para centralizar as janelas na janela principal, etc. Espero que tenham gostado!

Até a próxima 😉

<span>%d</span> blogueiros gostam disto: