Esse artigo faz parte da revista Clube Delphi Edição 90. Clique aqui para ler todos os artigos desta edição

Clique aqui para ler esse artigo em PDF. imagem_pdf.jpg

Mobile

PocketPC com Delphi

Desenvolvendo aplicativos móveis – Parte 2

 

Continuando o artigo da edição anterior, veremos agora como criar a aplicação cliente no Pocket PC para consumir o Web Service criado na última edição.

 

Criando a aplicação

O CF Builder inclui algumas opções no Repository do BDS para criação de aplicações para dispositivos móveis. Acesse o menu File>New>Other, na opção Smart Device escolha Smart Device Application para criar um novo projeto (Figura 1). Salve a unit principal com o nome “MainForm.pas” e o projeto como “PesquisaOpiniao.dpr”.

 

Figura 1. Opçõs Adicionadas pelo CF Builder no Repository

 

Para testarmos a aplicação no emulador acesse o menu Compact Framework>Run Smart Device Project no BDS, ou utilize o Play existente na barra de ferramentas adicionado pelo CF Builder (Figura 2).

 

Figura 2. Barra de Ferramentas do CF Builder no BDS

 

Na barra você pode selecionar o emulador que deseja rodar a aplicação. O executável é copiado automaticamente para a pasta compartilhada que você informou no campo Shared Folder nas configurações do emulador.

Assim, após rodar o emulador clicando no Play da barra do CF Builder, você deve acessar o Storage Card, como mostrado anteriormente, para executar a aplicação.

Lembre-se que se você não fizer um Soft Reset no emulador o Storage Card não aparecerá. Após o emulador abrir pela primeira vez, você não precisa fechá-lo, assim, o Storage Card não desaparece.

Para atualizar a aplicação no emulador, basta clicar no Play para que o executável seja copiado novamente para o Storage Card.

 

Acessando o Web Service na aplicação desktop

Para acessar o Web Service a partir da aplicação desktop, precisamos adicionar uma referência à URL do arquivo WSDL que descreve as funções disponíveis no Web Service.

Como vamos trabalhar com dois projetos ao mesmo tempo, e você já está com o projeto do Web Service aberto, para abrir o projeto da aplicação desktop clique com o botão direito em Project Group 1, no Project Manager, selecione Add Existing Project e localize o projeto PesquisaOpiniao.

Clique em Save All na barra de ferramentas para salvar o grupo de projetos. Salve com o nome de “Projetos Compact Framework”. Agora temos os dois projetos no Project Manager.

Dê um duplo clique no PesquisaOpiniao, a aplicação desktop para Compact Framework. Clique com o botão direito e escolha a opção Add Web Reference para adicionar uma referência ao Web Service criado.

Na janela que é aberta você deve informar a URL do Web Service, que se você utilizou os nomes que sugeri, deve ser “http://localhost/PesquisaOpiniaoWS/PesquisaOpiniao.asmx”. Porém, como a aplicação cliente não estará rodando no seu PC e sim no dispositivo móvel, por meio do emulador, você não deve informar localhost e sim o endereço IP do seu PC (onde está o servidor web com o WebService hospedado) ou o nome DNS dele.

No meu caso, utilizei o endereço “http://developer/PesquisaOpiniaoWS/PesquisaOpiniao.asmx”. Rode o projeto do Web Service que o navegador é aberto indicando a URL, porém, substitua localhost pelo IP ou nome DNS do seu PC.

Após informar a URL, pressione ENTER para abrir a página no navegador interno. Clique em Service Description para abrir o WSDL (o descritor do Web Service). O valor do campo Web reference folder name será usado como prefixo do namespace que será criado para acesso ao Web Service.

Altere o valor do campo para “PesquisaOpiniaoServer”. Depois clique em Add Reference para adicionar a referência ao projeto. O BDS cria um arquivo PAS mapeando as funções existentes no Web Service para serem usadas em nossa aplicação.

Agora inclua um MainMenu no formulário principal da aplicação desktop. Nele inclua um item “Arquivo” e dentro desse um item “Selecionar dados no Servidor”.  Para usarmos as funções do Web Service precisamos incluir a unit que foi criada após a referência ao Web Service ser adicionada ao projeto.

Assim, pressione ATL+F11 e selecione a unit PesquisaOpiniaoServer.PesquisaOpiniao. Inclua outros componentes no formulário para que esse fique semelhante à Figura 3.

 

Nota: Caso tente testar a aplicação e receba uma mensagem do tipo: File not found: 'MainForm.frmMain.resources', você deve fazer uma cópia do arquivo RESX colocando o mesmo nome da mensagem, sendo esse erro problema no BDS ou plugin.

 

Figura 3. Design do Formulário

Nota: Não crie um formulário muito grande, pois dependendo do equipamento, o mesmo pode ficar desconfigurado.

 

Para armazenar os dados na aplicação, utilizaremos o DataSet. Como as funções do Web Service, que retornam dados para a aplicação, devolvem DataSets, utilizaremos um DataSet para cada tabela que precisarmos armazenar dados localmente.

Assim, inclua cinco DataSets e altere seus nomes, respectivamente, para “dsPesquisa”, “dsPergunta”, “dsAlternativa”, “dsEntrevistado” e “dsResposta”. Na Tabela 1 temos as propriedades que devem ser alteradas para alguns componentes.

 

Componente

Propriedade

Valor

Descrição

ComboBox

Name

cbxPesquisa

 

ValueMember

cod_pesquisa

Contém os dados a serem usados como valores para cada um dos itens do componente. A propriedade DataSource será atribuída via programação.

DisplayMember

titulo

Contém os dados que serão exibidos no componente.

DropDownStyle

DropDownList

Não permite que o usuário digite qualquer coisa no componente, só podendo escolher um dos itens existentes nele.

TextBox

Name

txtEntrevistado

 

Label

Name

lbCodEntrevistado

 

ComboBox

Name

cbxPergunta

 

ValueMember

cod_pergunta

 

DisplayMember

titulo

 

DropDownStyle

DropDownList

 

ComboBox

Name

cbxResposta

 

ValueMember

cod_alternativa

 

DisplayMember

descricao

 

DropDownStyle

DropDownList

 

Tabela 1. Configuração dos componentes do formulário

 

Como precisaremos trabalhar com arquivos XML, criaremos uma unit que terá uma classe com funções estáticas (class function) que serão acessadas sem a necessidade de instanciar um objeto da classe.

Classes assim servem apenas para agrupar funções e constantes, organizando melhor o código. Crie a unit com o nome de “ClubeDelphi.PesquisaOpiniao.XML.pas” e adicione o código da Listagem 1.

 

Nota: O código possui comentários sobre as características de cada função.

 

Listagem 1. Funções para manipular os arquivos XML da aplicação

unit ClubeDelphi.PesquisaOpiniao.XML;

 

interface

 

uses System.Data, System.Reflection, System.IO,

  System.Windows.Forms;

 

type

  { Classe para agrupar as funções para manipulação

    dos DataSets }

  XMLCache = class

  private

    { Variável de classe: possui um único valor para

      todas as instâncias da classe. Ela é acessada

      sem a necessidade de instanciar um objeto da

      classe. Armazenará o caminho da aplicação }

    class var FAppDir: string;

  public

    { Constantes que definem o nome dos XML usados }

    const arqPesquisa = 'pesquisa.xml';

    const arqPergunta = 'pergunta.xml';

    const arqAlternativa = 'alternativa.xml';

    const arqEntrevistado = 'entrevistado.xml';

    const arqResposta = 'resposta.xml';

    { Descobre a pasta do executável }

    class function ApplicationDir: string;

    { Inclui um registro de entrevistado no DataSet

      passado por parâmetro }

    class function IncluirEntrevistado(

      var ds: DataSet; Nome: string;

      CodPesquisa: Integer): Integer;

    { Registra, no DataSet passado por parâmetro,

      uma resposta de um entrevistado }

    class function RegistrarResposta(

      var ds: DataSet; CodEntrevistado, CodPergunta,

      CodAlternativa: Integer): Boolean;

    { Carrega um XML, verificando se o mesmo existe,

      no DataSet passado por parâmetro }

    class function CarregarXML(var ds: DataSet;

      NomeArq: string): Boolean;

    { Grava os dados de um DataSet passado por

      parâmetro, em um arquivo XML }

    class procedure GravarXML(ds: DataSet;

      NomeArq: string);

    { Envia os dados dos entrevistados e seus

      respostas ao servidor }

    class procedure EnviarCacheXML(var dsEntrevistado,

      dsResposta: DataSet);

end;

 

A função XMLCache.ApplicationDir retorna o caminho onde a aplicação está. Foi usada uma variável de classe FAppDir para armazenar, quando a função for executada pela primeira vez, o caminho do executável, para não ter que ficar executando este código repetidamente. Veja o código na Listagem 2.

 

Listagem 2. Retorna o caminho onde a aplicação se encontra

class function XMLCache.ApplicationDir: string;

var

  caminho: string;

begin

 

  if FAppDir = '' then

  begin

    { Pega o caminho completo do Assembly em execução

      (a aplicação) }

    caminho := Assembly.GetExecutingAssembly.

      GetName().CodeBase;

    { Extrai somente a pasta onde da aplicação,

      incluindo uma barra no final }

    Result := System.String.Format('{0}{1}',

      Path.GetDirectoryName(caminho), '\');

    { Se a função for chamada a partir de uma

      aplicação desktop que não seja para Compact

      Framework, o texto file:\ aparecerá no início

      da string. O if a seguir remove este texto }

    if Result.StartsWith('file:\') then

      Result := Result.Substring(6, result.Length-6);

    FAppDir := Result;

  end

  else Result := FAppDir;

end;

 

A função XMLCache.CarregarXML (Listagem 3) verifica se o arquivo informado no parâmetro NomeArq existe para poder carregá-lo no DataSet passado por referência. Como o arquivo XML estará na pasta da aplicação, e estaremos usando o .NET Compact Framework, necessita que informe o caminho completo do arquivo que se deseja manipular.

 

Listagem 3. Carrega um arquivo XML passado como parâmetro

class function XMLCache.CarregarXML(var ds: DataSet;

  NomeArq: string): Boolean;

begin

  Result := &File.Exists(ApplicationDir + NomeArq);

  { Se o arquivo existe, carrega-o no DataSet passado

    por parâmetro }

  if result then

    ds.ReadXml(ApplicationDir + NomeArq);

end;

 

class procedure XMLCache.GravarXML(ds: DataSet;

  NomeArq: string);

begin

  { Grava os dados do DataSet no XML, na pasta da

    aplicação, com o nome passado por parâmetro }

  ds.WriteXml(ApplicationDir + NomeArq);

end;

 

Na função XMLCache.IncluirEntrevistado (Listagem 4), assim como nas outras funções de inclusão, como o registro dos entrevistados é feito localmente, o valor da chave primária é gerado localmente também, de acordo com o valor do último registro. Se o DataSet está vazio, o código para o registro a ser adicionado no DataSet será 1.

 

Listagem 4. Incluir um entrevistado

class function XMLCache.IncluirEntrevistado(

  var ds: DataSet; Nome: string;

  CodPesquisa: Integer): Integer;

var

  row: DataRow;

  total, cod: Integer;

begin

  total:= ds.Tables[0].Rows.Count;

  if total = 0 then

    cod := 1

  else

  begin

    row:= ds.Tables[0].Rows[total-1];

    { Gera um código local que será substituído no

      servidor quando os dados forem enviados }

    cod:= Convert.ToInt32(row['cod_entrevistado'])+1;

   end;

   { Cria uma nova linha (um novo registro) }

   row:= ds.Tables[0].NewRow;

   row['cod_entrevistado']:= cod.ToString;

   row['nome']:= Nome;

   row['cod_pesquisa']:= CodPesquisa.ToString;

   { Adiciona a linha no DataSet }

   ds.Tables[0].Rows.Add(row);

   Result := cod;

end;

 

class function XMLCache.RegistrarResposta(

  var ds: DataSet; CodEntrevistado, CodPergunta,

  CodAlternativa: Integer): Boolean;

var

  row: DataRow;

  total, cod: Integer;

begin

  total:= ds.Tables[0].Rows.Count;

  if total = 0 then

    cod := 1

  else

  begin

    row:= ds.Tables[0].Rows[total-1];

    { Gera um código local que será substituído no

      servidor }

    cod := Convert.ToInt32(row[

      'cod_resposta_entrevistado'])+1;

   end;

   row := ds.Tables[0].NewRow;

   row['cod_resposta_entrevistado'] := cod.ToString;

   row['cod_entrevistado'] :=

     CodEntrevistado.ToString;

   row['cod_pergunta'] := CodPergunta.ToString;

   row['cod_alternativa'] := CodAlternativa.ToString;

   ds.Tables[0].Rows.Add(row);

   Result := True;

end;

 

A função XMLCache.EnviarCacheXML (Listagem 5) envia os dados dos entrevistados, armazenados localmente no dispositivo em arquivo XML, e suas respostas ao Servidor, repassando os dados desses arquivos para as funções do Web Service.

 

Listagem 5. Salva os dados em Cache (arquivo XML)

uses PesquisaOpiniaoServer.PesquisaOpiniao;

...

class procedure XMLCache.EnviarCacheXML(

  var dsEntrevistado, dsResposta: DataSet);

var

  row: DataRow;

  row2: DataRowView;

  ws: TPesquisaOpiniaoWS;

  i, j, CodEntrevistado: Integer;

begin

  { Instancia a classe do WebService para acessar suas

    funções }

  ws := TPesquisaOpiniaoWS.Create;

  try

    { Percorre as linhas do DataSet de entrevistado

      para enviar essas linhas ao servidor para serem

      incluídas no banco }

    for i := dsEntrevistado.Tables[0].Rows.Count

      - 1 downto 0 do

    begin

      { Pega a linha atual }

      row := dsEntrevistado.Tables[0].Rows[i];

      { Passa os dados da linha atual do DataSet para

        a função IncluirEntrevistado do WebService,

        para incluir estes dados no banco, retornando

        o código gerado pelo WebService para o registro

        do entrevistado }

      CodEntrevistado := ws.IncluirEntrevistado(

        row['nome'].ToString, Convert.ToInt32(

        row['cod_pesquisa']));

      { Se retornou maior que zero é porque o

        registro foi incluído }

      if CodEntrevistado > 0 then

      begin

        { Filtra o DataSet de respostas retornando

          somente as respostas do entrevistado do

          entrevistado que foi adicionado ao banco

          (utilizando o código existente na linha

          atual (row) do DataSet dsEntrevistado) }

        dsResposta.Tables[0].DefaultView.RowFilter :=

          'cod_entrevistado=' + row[

          'cod_entrevistado'].ToString;

        { Remove o registro do entrevistado do DataSet,

          pois esse já foi enviado ao servidor }

        dsEntrevistado.Tables[0].Rows.RemoveAt(i);

        { Percorre o DataSet dsResposta pegando todas

          as respostas do entrevistado atual }

        for j := dsResposta.Tables[0].DefaultView.

          Count-1 downto 0 do

        begin

          { Pega a linha atual do DataSet dsResposta }

          row2 := dsResposta.Tables[

            0].DefaultView.Item[j];

          { Chama a função RegistrarResposta do

            WebService, para incluir no banco os dados

            do registro atual do DataSet dsResposta.

            Se a função retornar True, indica que a

            resposta foi incluída no banco(ou

            atualizada), assim, remove do DataSet,

            dsResposta o registro na posição atual(j) }

          if ws.RegistrarResposta(CodEntrevistado,

            Convert.ToInt32(row2['cod_pergunta']),

            Convert.ToInt32(row2[

            'cod_alternativa'])) then

              dsResposta.Tables[

                0].DefaultView.Delete(j);

        end;

      end;

    end;

  finally

    { Grava as alterações nos DataSet's em arquivo }

    dsEntrevistado.WriteXml(arqEntrevistado);

    dsResposta.WriteXml(arqResposta);

  end;

end;

end.

 

Abra o formulário principal e pressione ALT+F11 para usar a unit recém criada. No evento Click do último item de menu criado, vamos solicitar ao usuário que digite o título da pesquisa (ou parte dele) para que seja feito o acesso ao Web Service e retornados os dados da pesquisa.

Como a aplicação estará rodando em um dispositivo móvel, nem sempre teremos conectividade para acessar o Web Service, pois, como é um sistema de pesquisa, o usuário poderá estar em qualquer parte da cidade, sem acesso à internet para fazer a comunicação com o servidor.

Assim, precisaremos carregar os dados necessários para o dispositivo e guardar localmente em arquivos XML, antes do usuário ir à rua para fazer seu trabalho de pesquisa. Quando o usuário estiver na rua, os dados das entrevistas que fizer devem ser armazenados localmente em arquivos XML, para quando ele chegar a um local que tenha acesso à internet ou à rede local da empresa, transferir os dados ao servidor. Para isso que servirão os DataSets adicionados.

Para solicitar ao usuário o título da pesquisa, quando clicar no menu, poderíamos utilizar uma função como InputBox do Delphi Win32 (que abre uma janela solicitando um dado do usuário), porém, não temos uma função semelhante em Delphi for .NET.

Dessa forma, criaremos uma classe que implementará um InputBox. Crie uma nova unit e salve com o nome de “ClubeDelphi.InputBox.pas”. O código dessa unit é mostrado na Listagem 6. Para usar o InputBox chame a função de classe InputBox.Show passando os devidos parâmetros (a função é chamada a partir da classe e não de uma instância).

 

Listagem 6. Unit ClubeDelphi.InputBox.pas

unit ClubeDelphi.InputBox;

 

interface

 

uses System.Drawing, System.Windows.Forms,

  System.ComponentModel;

 

type

  InputBox = class(System.Windows.Forms.Form)

  private

    TextBox1: System.Windows.Forms.TextBox;

    Label1: System.Windows.Forms.&Label;

    btnOK: System.Windows.Forms.Button;

    btnCancel: System.Windows.Forms.Button;

    constructor Create();

    procedure TextBox1_KeyDown(sender: &Object;

      e: System.Windows.Forms.KeyEventArgs);

    procedure InitializeComponent();

  public

    class function Show(Title,

      Question: string): string;

end;

 

implementation

 

constructor InputBox.Create();

begin

  inherited Create;

  InitializeComponent();

end;

 

procedure InputBox.TextBox1_KeyDown(sender: &Object;

  e: System.Windows.Forms.KeyEventArgs);

begin

  if e.KeyCode = Keys.Enter then

    Self.DialogResult :=

      System.Windows.Forms.DialogResult.OK;

end;

 

procedure InputBox.InitializeComponent();

begin

  Self.Label1:= System.Windows.Forms.&Label.Create;

  Self.TextBox1 :=

    System.Windows.Forms.TextBox.Create;

  Self.btnOK := System.Windows.Forms.Button.Create;

  Self.btnCancel :=

    System.Windows.Forms.Button.Create;

 

  { As propriedades e métodos que estão dentro da

    diretiva $IFNDEF CF só serão compiladas se não

    estivermos compilando para o Compact Framework }

    {$IFNDEF CF}

    Self.SuspendLayout();

    {$ENDIF}

 

    Self.TextBox1.Location :=

      System.Drawing.Point.Create(16, 16);

    {$IFNDEF CF}

    Self.TextBox1.Name := 'textBox1';

    Self.TextBox1.TabIndex := 0;

    {$ENDIF}

 

    Self.TextBox1.Size := System.Drawing.Size.Create(

      180, 20);

 

    Self.TextBox1.Text := '';

    Self.TextBox1.Top := 28;

    Self.TextBox1.Left := 10;

    Include(Self.TextBox1.KeyDown,

      Self.TextBox1_KeyDown);

 

    Self.Label1.Text:= 'Digite o dado';

    {$IFNDEF CF}

    Self.Label1.AutoSize := true;

    Self.Label1.TabIndex := 1;

    {$ELSE}

    Self.Label1.Size.Width :=

      Self.TextBox1.Size.Width;

    {$ENDIF}

    Self.Label1.Top := 10;

    Self.Label1.Left := 10;

 

    Self.btnOK.Text:= 'OK';

    Self.btnOK.DialogResult :=

      System.Windows.Forms.DialogResult.OK;

    {$IFNDEF CF}

    Self.btnOK.TabIndex := 2;

    Self.btnOK.NotifyDefault(true);

    {$ENDIF}

    Self.btnOK.Top := 55;

 

    Self.btnOK.Left := 10;

 

    Self.btnCancel.Text:= 'Cancelar';

    {$IFNDEF CF}

    Self.btnCancel.TabIndex := 3;

    {$ENDIF}

    Self.btnCancel.Top := 55;

    Self.btnCancel.Left := 100;

    Self.btnCancel.DialogResult :=

      System.Windows.Forms.DialogResult.Cancel;

 

    {$IFNDEF CF}

    Self.AutoScaleBaseSize :=

      System.Drawing.Size.Create(5, 13);

    {$ENDIF}

    Self.ClientSize := System.Drawing.Size.Create(

      220, 90);

    Self.ControlBox := false;

    Self.Controls.Add(Self.TextBox1);

    Self.Controls.Add(Self.Label1);

    Self.Controls.Add(Self.btnOK);

    Self.Controls.Add(Self.btnCancel);

    Self.FormBorderStyle :=

      System.Windows.Forms.FormBorderStyle.FixedDialog;

    {$IFNDEF CF}

    Self.Name := 'InputBox';

    Self.ResumeLayout(false);

    {$ENDIF}

end;

 

A função do InputBox.Show é chamar o InputBox sem a necessidade de criar um objeto da classe. Para chamá-lo basta fazer InputBox.Show passando os parâmetros, sem criar objeto algum, com o código da Listagem 7.

 

Listagem 7. Método Show do InputBox

class function InputBox.Show(Title,

  Question: string): string;

var

  box: InputBox;

begin

  box := InputBox.Create;

  box.Text := Title;

  box.Label1.Text := Question;

  if box.ShowDialog =

    System.Windows.Forms.DialogResult.OK then

    Result := box.TextBox1.Text

  else Result:= '';

end;

 

Precisamos agora incluir o código para buscar os dados das perguntas e alternativas no servidor. No formulário principal, pressione ALT+F11 para usar a unit recém criada. No evento Click do item de menu adicione o código da Listagem 8.

 

Listagem 8. Menu Selecionar dados no Servidor

var

  ws: TPesquisaOpiniaoWS;

  titulo: string;

  codPesquisa: Integer;

begin

  { Chama o InputBox para solicitar o título da

    pesquisa ao usuário }

  titulo := InputBox.Show('Selecionar Pesquisa',

    'Título da pesquisa');

  if titulo <> '' then

  begin

    { Instancia um objeto da classe do WebService }

    ws := TPesquisaOpiniaoWS.Create;

    { Busca os dados da pesquisa no WebService }

    dsPesquisa := ws.GetPesquisa(titulo);

    if (dsPesquisa.Tables[0].Rows.Count > 0) then

    begin

      { Liga a tabela do DataSet para que as

        pesquisas sejam exibidas no ComboBox }

      cbxPesquisa.DataSource := dsPesquisa.Tables[0];

      { Guarda o código da pesquisa retornada }

      codPesquisa := Convert.ToInt32(

        dsPesquisa.Tables[0].Rows[0]['cod_pesquisa']);

      { Armazena os dados da pesquisa localmente em um

        XML. XMLCache é a classe que criamos para

        agrupar as funções para manipulação dos

        DataSets }   

      XMLCache.GravarXML(dsPesquisa,

        XMLCache.arqPesquisa);

      dsPergunta := ws.GetPerguntas(codPesquisa);

      XMLCache.GravarXML(dsPergunta,

        XMLCache.arqPergunta);

      { Se existe alguma pergunta cadastrada para a

        pesquisa }

      if dsPergunta.Tables[0].Rows.Count > 0 then

      begin

        dsAlternativa:= ws.GetAlternativas(

          codPesquisa);

        XMLCache.GravarXML(dsAlternativa,

          XMLCache.arqAlternativa);

        { Chama a função GetEntrevistado do WebService

          informando um nome de pessoa que não vai

          existir, para que seja retornado um DataSet

          vazio, apenas com a estrutura da tabela para

          que se possa armazenar os dados localmente

          neste DataSet }

        dsEntrevistado:= ws.GetEntrevistado('nenhum');

        XMLCache.GravarXML(dsEntrevistado,

          XMLCache.arqEntrevistado);

        { A mesma lógica anterior }

        dsResposta := ws.GetRespostaEntrevistado(-1);

        XMLCache.GravarXML(dsResposta,

          XMLCache.arqResposta);

      end;

    end

    else

      MessageBox.Show('Nenhuma pesquisa retornada',

       'Informação');

  end;

end;

 

Esse código chama as funções existentes no Web Service para retornar os dados que desejamos e em seguida estes dados são armazenados localmente em arquivos XML para a aplicação poder trabalhar off-line.

Cadastre algumas pesquisas, compile e execute a aplicação (usando os botões da barra de ferramentas do CF Builder) e clique no menu para ver o resultado. O emulador padrão é aberto.

Acesse o Storage Card para executar o programa. Lembre-se de usar o Soft Reset caso o emulador não estivesse aberto anteriormente.

 

Utilizando o Device Emulator

A aplicação, acessando o Web Service a partir do WindowsCE Emulator não se comportou muito bem. Assim, você pode utilizar o Device Emulator para executar o programa. Como já mencionado, esse não pode ser configurado totalmente usando o CF Builder.

Assim, vamos usar os links que são criados na pasta Microsoft Windows Mobile 5.0 Emulator no menu Iniciar para executá-lo. Na pasta do menu Iniciar, indicada anteriormente, existem seis atalhos para imagens de dispositivos. Três terminam com a palavra Coldboot e outras três com a palavra Savestate.

Os Coldboot iniciarão o dispositivo dando um boot completo, o que vai demorar bastante. Os Savestate iniciam o dispositivo a partir de uma sessão salva anteriormente, o que será bem rápido.

Os atalhos Savestate só vão funcionar depois que cada uma das sessões dos atalhos Coldboot forem salvas. Ao salvar a sessão no emulador é criado um arquivo DESS, permitindo reiniciar a imagem a partir do mesmo ponto onde ela parou de rodar quando o emulador foi fechado. Para salvar a sessão no emulador, acesse o menu File e clique na opção Save State and Exit.

Para configurar o Storage Card para as imagens, você terá que configurar cada uma das Coldboot. Assim, clique com o botão direito em uma e escolha Propriedades. Na aba Atalho, no campo Destino, adicione no final a linha “/sharedfolder PastaQueDesejarUsarComoStorageCard”.

Se o caminho da pasta a ser utilizada tiver espaço, coloque todo o caminho “entre aspas”. Após configurar o Storage Card nos três atalhos Coldboot, execute algum deles.

Para abrir o Storage Card, clique no menu Iniciar do emulador e escolha File Explorer. Na parte superior da janela existe um botão onde você pode selecionar Storage Card para abrir a pasta compartilhada no seu computador e acessar os arquivos lá.

 

Configurando a rede no Device Emulator

Com as imagens de Windows Mobile (o Device Emulator só usa essas) o acesso à Internet ou a uma rede local é feito por Bluetooth, infravermelho ou via ActiveSync, esse último quando se conecta o dispositivo a um PC. Temos também como emular essa conexão do emulador (o dispositivo que temos) ao PC.

Você precisará que o Device Emulator Manager (encontrado na mesma pasta do menu iniciar indicada anteriormente) e o ActiveSync estejam rodando. O Active Sync disponibiliza o ícone  no System Tray.

Clique com o botão direito nele e escolha a opção Configurações de conexão. Na janela que abre, marque a opção Permitir conexão com um dos seguintes itens e no campo abaixo, escolha DMA para permitir conectar o computador virtualmente ao emulador, sem uma conexão física. Clique em OK.

Após rodar o emulador, no Microsoft Device Emulator deverá aparecer a referência para o dispositivo, se não, clique no Refresh. Com o botão direito sobre o link para dispositivo, dentro do Microsoft Device Emulator, escolha a opção Cradle, para simular a conexão do dispositivo ao PC.

O ícone do ActiveSync deve ativar, ficando verde, indicando que está conectando ao dispositivo. Após ele concluir a conexão, você poderá acessar uma rede local ou à internet a partir do dispositivo, sem nenhuma configuração adicional.

Caso o dispositivo não seja conectado pelo ActiveSync automaticamente, clique com o botão direito no ícone do Active Sync no System Tray e escolha a opção Configurações de conexão. Na janela que abre, clique em Conectar.

Após o emulador estar conectado no ActiveSync, acesse o Storage Card e execute a aplicação. Para atualizar o sistema, basta copiar o executável para a pasta compartilhada e executar novamente no emulador, sem precisar fechá-lo.

 

Continuando o desenvolvimento da aplicação cliente

Depois de ter testado o Device Emulator, vamos continuar no desenvolvimento da aplicação. Quando o usuário selecionar uma pergunta, devemos mostrar no ComboBox de alternativas, somente as referentes à pergunta selecionada. Assim, no evento SelectedValueChanged do cbxPergunta adicione o código da Listagem 9.

 

Listagem 9. SelectedValueChanged do cbxPergunta

var

  codPergunta: Integer;

begin

  { Se há alguma pergunta selecionada, mostra no

    ComboBox de alternativa (cbxResposta), somente

    as referentes à pergunta selecionada }

  if (cbxPergunta.SelectedIndex <> -1) and

    (cbxPergunta.SelectedValue <> nil) then

  begin

    { Pega o código da pergunta selecionada }

    codPergunta := Convert.ToInt32(

      cbxPergunta.SelectedValue);

    { Filtra o DataSet de alternativas para exibir

      apenas as alternativas da resposta selecionada }

    dsAlternativa.Tables[0].DefaultView.RowFilter :=

      'cod_pergunta=' + codPergunta.ToString;

    { Vincula os registros filtrados ao ComboBox das

      Alternativas }

    cbxResposta.DataSource :=

      dsAlternativa.Tables[0].DefaultView;

    { Não deixa nenhum item marcado no ComboBox }

     cbxResposta.SelectedIndex:= -1;

  end;

end;

 

Agora inclua o código para o Click do Incluir, que fará a inclusão do entrevistado no DataSet local, conforme a Listagem 10.

 

Listagem 10. Incluir Entrevistado

public

  CodEntrevistado: integer;

...

begin

{ Inclui o entrevistado no DataSet local, retornando

  o código temporário gerado para o registro,

  pois este código é alterado quando o registro

  é enviado ao banco de dados }

CodEntrevistado :=

  XMLCache.IncluirEntrevistado(dsEntrevistado,

  txtEntrevistado.Text,

Convert.ToInt32(cbxPesquisa.SelectedValue));

{ Exibe o código gerado }

lbCodEntrevistado.Text := 'Código: ' +

  CodEntrevistado.ToString;

{ Desvincula o procedimento

  cbxPergunta_SelectedValueChanged do evento

  SelectedValueChanged do cbxPergunta enquanto os

  dados são vinculados ao cbxPergunta, para evitar que

  o evento seja disparado no momento errado }

Exclude(cbxPergunta.SelectedValueChanged,

  cbxPergunta_SelectedValueChanged);

try

  { Vincula os dados do DataSet dsPergunta ao

    cbxPergunta para que as perguntas sejam exibidas

    no ComboBox }

  cbxPergunta.DataSource := dsPergunta.Tables[0];

finally

  { Vincula novamente o procedimento

    cbxPergunta_SelectedValueChanged  ao evento

    SelectedValueChanged do cbxPergunta }

  Include(cbxPergunta.SelectedValueChanged,

    cbxPergunta_SelectedValueChanged);

end;

{ Desmarca a pergunta atualmente selecionada }

cbxPergunta.SelectedIndex:= -1;

{ Manda o cursor para o campo }

cbxPergunta.Focus;

 

Crie os procedimentos “PerguntaAnterior” e “ProximaPergunta” para permitir ao usuário navegar entre as perguntas disponíveis. Veja o código dos dois procedimentos na Listagem 11.

 

Listagem 11. Procedimentos para navegar entre as perguntas

procedure frmMain.PerguntaAnterior;

begin

  { Volta à pergunta anterior }

  if cbxPergunta.SelectedIndex > 0 then

    cbxPergunta.SelectedIndex :=

      cbxPergunta.SelectedIndex - 1;

  cbxPergunta.Focus;

end;

 

procedure frmMain.ProximaPergunta;

begin

  { Avança à próxima pergunta }

  if cbxPergunta.SelectedIndex <

    cbxPergunta.Items.Count - 1 then

      cbxPergunta.SelectedIndex :=

        cbxPergunta.SelectedIndex + 1;

  cbxPergunta.Focus;

end;

 

Agora chame os procedimentos nos seus respectivos botões. No evento Click do Registrar digite o código da Listagem 12, para registrar a resposta do entrevistado no DataSet local.

 

Listagem 12.  Registrar a resposta do entrevistado

{ Registra a resposta do entrevistado no DataSet local

  e passa para a próxima pergunta }

XMLCache.RegistrarResposta(dsResposta,

  CodEntrevistado, Convert.ToInt32(

  cbxPergunta.SelectedValue), Convert.ToInt32(

  cbxResposta.SelectedValue));

ProximaPergunta;

 

Testando a aplicação

Compile a aplicação (usando a barra do CF Builder), copie o executável para a pasta compartilhada e execute-o a partir do Storage Card no Device Emulator. Você deverá acessar o menu Selecionar dados no Servidor, caso ainda não tenha feito isso, para guardar os dados da pesquisa, as perguntas e alternativas, localmente em arquivos XML no dispositivo móvel.

Digite o nome do entrevistado e clique em Incluir para registrá-lo. Em seguida, selecione a pergunta, depois a resposta e clique em Registrar. Repita esse último passo até responder todas as perguntas.

Para incluir um novo questionário de um entrevistado, digite o nome do mesmo e clique em Incluir, repetindo todo o processo.

 

Salvando em disco

Vamos agora fazer com que os dados sejam salvos em disco quando o sistema for fechado. Poderíamos fazer isso em outro evento também, como a cada pergunta respondida. Assim, crie a função da Listagem 13 e depois chame-a no evento Closed do FrmMain.

 

Listagem 13. Salva os dados dos DataSets em arquivos XML

procedure frmMain.SalvarCacheLocal;

begin

  { Se existe alguma tabela no DataSet e existe algum

    registro nela, salva os dados em arquivo }

  if (dsEntrevistado.Tables.Count > 0) and

    (dsEntrevistado.Tables[0].Rows.Count > 0) then

    XMLCache.GravarXML(dsEntrevistado,

      XMLCache.arqEntrevistado);

  if (dsResposta.Tables.Count > 0) and

    (dsResposta.Tables[0].Rows.Count > 0) then

    XMLCache.GravarXML(dsResposta,

      XMLCache.arqResposta);

end;

 

Agora que fizemos a rotina para chamar a função para salvar os DataSets localmente, precisamos criar o código para carregar esses arquivos quando a aplicação inicializar.  Assim, digite o código da Listagem 14 no evento Load do FrmMain.

 

Listagem 14. Evento Load do FrmMain para carregar os arquivos XML

{ Se carregou o arquivo de pesquisas, carrega os

  outros em seguida }

if XMLCache.CarregarXML(dsPesquisa,

  XMLCache.arqPesquisa) then

begin

  { Vincula os dados do DataSet dsPesquisa ao

    cbxPesquisa para exibir o título da pesquisa no

    ComboBox }

  cbxPesquisa.DataSource := dsPesquisa.Tables[0];

  { Se carregou o arquivo de perguntas, então carrega

    o de alternativas }

  if XMLCache.CarregarXML(dsPergunta,

    XMLCache.arqPergunta) then

    XMLCache.CarregarXML(dsAlternativa,

      XMLCache.arqAlternativa);

  { Se carregou o arquivo de entrevistados, então

    carrega o de respostas }

  if XMLCache.CarregarXML(dsEntrevistado,

    XMLCache.arqEntrevistado) then

    XMLCache.CarregarXML(dsResposta,

      XMLCache.arqResposta);

end;

 

Quando o usuário tiver conexão com a internet ou diretamente à rede local da empresa, ele deve enviar os dados ao servidor para serem cadastrados no banco de dados, usando o EnviarCacheXML criada anteriormente.

No FrmMain inclua um novo item no MainMenu com o título (propriedade Text) igual a “Enviar dados ao Servidor”. No Click deste item inclua o código a seguir para chamar a função recém-criada, que enviará os dados armazenados localmente em XML ao servidor:

 

XMLCache.EnviarCacheXML(dsEntrevistado, dsResposta);

 

Rode a aplicação e faça os testes necessários (Figura 4).

 

Figura 4. Aplicação sendo testada no Pocket PC

 

Conclusão

Pronto, agora é só fazer os testes. Alguns detalhes para melhoria da interface não foram incluídos, como a habilitação/desabilitação dos botões nos momentos adequados, para evitar problemas caso o usuário não siga a ordem correta, mas isso é bem simples de fazer e fica para vocês poderem melhorar a aplicação.

Um recurso muito interessante seria a inclusão de um CheckBox para informar se é para trabalhar off-line. Estando desmarcado, a aplicação já mandaria os dados diretamente para o servidor.

Isso também é fácil de implementar, basta chamar as devidas funções, que já estão implementadas. Outro detalhe, que é bastante importante, é quanto ao controle de transações que não foi aplicado, para garantir, por exemplo, que o registro do entrevistado com os registros das respostas sejam todos incluídos ou, em caso de erro, nenhum ser incluído.

Também não me preocupei em verificar se o usuário informou valores para os campos, antes de inserir os dados. Assim, fica o homework para vocês fazerem. Neste artigo procurei mostrar a maioria dos detalhes quanto à montagem do ambiente de desenvolvimento, configuração e utilização das ferramentas assim como macetes para permitir que você ingresse no mundo do desenvolvimento de aplicações para dispositivos móveis.

Espero que tenham gostado e até o próximo.

 

ClubeDelphi PLUS!

Acesse agora o mesmo o portal do assinante ClubeDelphi e assista a uma série de vídeo aulas de Jefferson Junglaus, sobre o Compact Framework no Delphi.  www.devmedia.com.br/articles/listcomp.asp?txtsearch=Compact+Framework+no+Delphi