msdn05_capa.JPG

Clique aqui para ler todos os artigos desta edição

 

Desenvolvendo Aplicações Conectadas

Parte II – Desenvolvendo as camadas da aplicação

por Wallace Cézar Sales dos Santos

O objetivo deste treinamento é demonstrar como desenvolver aplicações conectadas a bases de dados ou outros repositórios de informação, utilizando os recursos existentes no Visual Studio .Net.

 

Introdução

 

Na primeira parte deste treinamento (publicada na edição 4 - fevereiro/2004), definimos os objetivos e como seria feita a aplicação do Gerenciamento de Atividades. Foi definido o modelo UML, baseado em MDA, onde projetamos todas as classes e objetos que irão compor o sistema. Criamos a solução no Visual Studio de acordo com o modelo que continha cada uma das classes e a dividimos em 3 projetos: ActivityManagerBO (com os objetos de negócio da aplicação), ActivityManagerData (para realizar o acesso ao banco de dados) e ActivityManagerUI (com o executável da aplicação, contendo as User Interfaces necessárias). Adicionamos ainda o projeto ActivityManagerDB, com os scripts de criação do banco de dados.

Realizamos também a montagem do banco de dados e definimos os objetos necessários para a aplicação, utilizando os recursos do Visual Studio para executar essas tarefas.

Definindo o acesso ao banco de dados

O primeiro objeto definido na aplicação é o ActivityManagerData. Trata-se de um objeto genérico, que visa abstrair o acesso ao banco de dados da user interface e dos objetos de negócio e, com isso, fornecer reutilização de código e recursos existentes no sistema. Essa classe encapsula os recursos do ADO.Net necessários à aplicação.

O ADO.Net é a versão de objetos de acesso a banco de dados utilizada no.Net. Ele não deve de forma alguma ser confundido como a próxima versão do ADO, embora existam algumas semelhanças, como objetos Connection e Command. Conceitualmente, as finalidades são muito diferentes. O ADO.Net é voltado para o desenvolvimento chamado “fracamente conectado”, que é o ambiente nativo da Internet. Por esse motivo, traz novidades como o DataSet, um objeto que permite manter na memória uma ou mais tabelas do banco de dados e, com isso, trabalhar com as informações desconectado do banco de dados, utilizando nativamente o XML na sua estrutura interna.

O ActivityManagerData foi desenhado para acesso exclusivo ao Microsoft SQL Server e possui os seguintes métodos públicos principais (com suas respectivas sobrecargas): ReturnReader; ReturnDataSet, ReturnXmlReader, ExecuteNonQuery e ExecuteScalar. Com esses métodos, cobrimos todas as necessidades de acesso ao banco de dados, não somente da nossa aplicação.

O método ReturnReader retorna um objeto tipo SqlClient.SqlDataReader. O SqlDataReader faz a implementação do objeto RecordSet Foward-Only / Read-Only para o ambiente .Net. É o acesso ideal para operações onde é necessário preencher listas, por exemplo, objetos ComboBox. Não podemos instanciar diretamente uma classe DataReader. Para isso, usamos o método ExecuteDataReader do objeto Command. Se desejar, você pode implementar sua versão de DataReader através da assinatura da interface IDataReader, que fornece o modelo a ser adotado. A Listagem 1 apresenta a implementação do método ReturnReader:

Listagem 1:  Implementação do método ReturnReader

Public Shared Function ReturnReader(_

   ByVal commandText As String, _

   ByVal commandType As System.Data.CommandType, _

   ByVal ParamArray parameters() As SqlParameter) __

   As SqlDataReader

   PreparaCommand(commandText, commandType, parameters)

   Dim dr As SqlClient.SqlDataReader

   Try

     dr = _command.ExecuteReader

   Catch ex As Exception

     dr.Close()

     Throw New DataException(ex.Message)

   End Try

   Return dr

End Function

 

O detalhe no uso do DataReader é que este objeto trabalha conectado ao banco de dados e, por este motivo, devemos ter o cuidado de fechar a conexão após usá-lo. Isso pode ser feito de duas formas: automática e manual. Na forma automática, esse procedimento é feito juntamente com a execução do método ExecuteDataReader, que pode receber como argumento a enumeração CommandBehavior.CloseConnection (a mais indicada), pois a conexão é fechada imediatamente após o uso do DataReader. Na forma manual, esse procedimento é feito através da chamada do método Close do objeto Connection.

O método ReturnDataSet retorna um objeto do tipo DataSet, ideal para as operações desconectadas. O DataSet implementa o modelo XML nativamente. Ele foi projetado para ser usado nas aplicações desenvolvidas para o ambiente Web. Outro ponto forte do DataSet é que ele pode ser utilizado da mesma forma para manipular dados extraídos de quaquer outra fonte de dados, como um arquivo XML. O grande detalhe é que o DataSet não acessa diretamente a fonte de dados. Ele precisa de uma “ponte”, que é feita por um objeto DataAdapter. A Figura 1 demonstra como funciona.

 

image002.jpg

Figura 1: Arquitetura de uso do DataSet.

 

O preenchimento do DataSet é realizado pelo método Fill do objeto DataAdapter: DataAdapter.Fill (DataSet). Observe que passamos um objeto DataSet como argumento. Para fazer uso de seus recursos, basta conhecer o modelo relacional tradicional. Isso mesmo, o DataSet tem no seu modelo os objetos que traduzem as estruturas relacionais: tabela, coluna, linha, chaves primária e estrangeira e relacionamentos. Nesta aplicação, não faremos uso de DataSet devido ao ambiente em que ela é executada - aplicação local.

O método ExecuteXmlReader é para ser usado exclusivamente com SQL Server e é aplicado somente pela classe SqlCommand. O objetivo dessa limitação é suportar as funcionalidades nativas do Microsoft SQL Server no tratamento de XML, utilizando-o junto com a cláusula FOR XML, como podemos verificar no código a seguir (que implementa o método ExecuteXmlReader). O retorno é um objeto XmlReader. Como todo “reader”, ele trabalha conectado e necessita ser fechado após o uso.

 

Dim myCmd As New SqlCommand(“SELECT Assunto, DataInicio, DataTermino FROM Activity FOR XML AUTO, ELEMENTS”, myConn)
Dim reader AS XmlReader = myCmd.ExecuteXmlReader()

 

O método ExecuteNonQuery é utilizado para executar sentenças SQL que não retornam resultados. Porém, ele retorna um valor inteiro que representa o número de linhas que foram afetadas com a operação. Esse é um detalhe importante, pois já vi profissionais acreditarem que esse valor de retorno devesse ser um valor diferente (como um campo tipo “Identity”) durante uma inserção de dados. É com esse método que executaremos todas as sentenças de Insert, Delete e Update do banco de dados. A Listagem 2 mostra a implementação na classe.

Listagem 2: Implementação do método ExecuteNonQuery

Public Shared Function ExecuteNonQuery( _

  ByVal commandText As String, _

  ByVal commandType As System.Data.CommandType, _

  ByVal ParamArray parameters() As SqlParameter) _

  As Integer

  PreparaCommand(commandText, commandType, parameters)

  Try

    Return_command.ExecuteNonQuery()

  Catch ex As Exception

    Throw New DataException(ex.Message)

  End Try

End Function

 

Por fim, temos o método ExecuteScalar que é utilizado para executar consultas ao banco de dados, retornando um tipo Object, ou seja, ele não retorna nenhum tipo de coleção. Assim sendo, não podemos retornar conjuntos de registros, como fazemos com o DataReader ou DataSet, mas podemos retornar o registro existente na primeira coluna da primeira linha dos registros encontrados. É o ideal para situações onde desejamos como resultado um valor agregado do banco de dados.

Nas Listagens 1 e 2, observado o trecho de código, você notará que existe a chamada a um método: PreparaCommand (Listagem 3), que é responsável por configurar o objeto Command da classe.

Listagem 3:  Método PreparaCommand

Private Shared Sub PreparaCommand( _

  ByVal commandText As String, _
  ByVal
commandType As System.Data.CommandType, _

  ByVal ParamArray parameters() As SqlParameter)

  If Not (connection.State = ConnectionState.Open) _

     Then _connection.Open()

  If (command Is Nothing) Then _

  _command = New SqlCommand

  _command.Connection = _connection

  _command.CommandText = commandText

  _command.CommandType = commandType

  _command.Parameters.Clear()

  If Not (parameters Is Nothing) Then

     For Each p As SqlParameter In parameters

         If Not (p Is Nothing) Then _

           _command.Parameters.Add(p)

     Next

  End If

End Sub

 

Na primeira linha do método PreparaCommand, verificamos se a conexão com o banco de dados está aberta. Essa verificação é realizada através do valor existente na propriedade State, com os valores definidos pela enumeração ConnectionState do objeto Connection. Só abrimos uma conexão se o estado for diferente de ConnectionState.Open. Da mesma forma, verificamos se já existe uma instância do objeto Command. Caso não haja (objeto definido como Nothing), criamos a instância. Os próximos passos para configurar o comando é atribuir a conexão através da propriedade Command.Connection, pois o comando precisa de uma conexão aberta para poder ser executado; para informar o comando que será executado, usamos a propriedade Command.CommandText; para informar o tipo de comando que será executado, usamos a propriedade Command.CommandType (valores definidos pela enumeração CommandType: StoredProcedure, Text e Table).

A última tarefa a ser executada é configurar os possíveis parâmetros a serem atribuídos ao comando. Neste ponto, fica mais evidente um detalhe arquitetural importante: observe que, antes de atribuímos os parâmetros informados para a propriedade Command.Parameters, limpamos esta coleção através da chamada ao método Command.Parameters.Clear. Fazemos isso porque os objetos Connection e Command, declarados em nível de classe, são definidos como Shared. Isso quer dizer que sua instância é compartilhada. Nesta situação, se o comando anterior possuir parâmetros e o seguinte não e se não fizermos essa “limpeza”, teremos um erro na execução do comando. Essa implementação é baseada no design pattern Singleton, permitindo assim uma excelente otimização dos recursos utilizados no acesso ao banco de dados e no gerenciamento de memória. Cabe ressaltar que numa aplicação como esta, não faz sentido ter várias instâncias de objetos connection ou command compartilhando a memória.

Definindo os objetos de negócio

Os objetos de negócio são o que se pode chamar de  “a grande sacada” no desenvolvimento de softwares. Eles são responsáveis por permitir o uso de uma das grandes vantagens da orientação a objetos: a reutilização. Com o uso de objetos de negócio, retiramos a inteligência da aplicação da User Interface, permitindo assim que nossa aplicação seja facilmente portada para outros ambientes com o mínimo de esforço (por exemplo, para portar uma aplicação Windows para a Web ou vice-versa).

 

Para a aplicação, foram planejados os seguintes objetos de negócio (principais):

·         Activity – representa o objeto atividade gerenciado pelo sistema;

·         ActivityCollection – representa uma coleção de objetos Activity;

·         Attachment – representa o objeto anexo vinculadas as atividades gerenciadas;

·         AttachmentCollection – representa uma coleção de objetos Attachment;

·         Configuration – representa o objeto responsável pelas configurações do sistema.

 

Também possuímos definidas algumas enumerações, que apoiam nosso modelo, bem como classes derivadas específicas para tratamento de excessões.

Os objetos Activity e Attachment refletem basicamente as tabelas existentes no banco de dados, conforme explicamos no artigo anterior (parte I, publicado em fevereiro/2004). As propriedades das classes refletem os campos existentes nas tabelas do banco de dados. Os detalhes interessantes estão nas operações existentes nessas classes: Carregar, Salvar e Apagar. São esses métodos que fazem a ponte com o objeto de acesso a dados e com o banco de dados. Na Listagem 4, podemos observar a implementação do método Salvar da classe Activity.

O primeiro ponto a ser observado é o argumento isNew, que definirá se este é um novo objeto ou a atualização do objeto atual. Também demostramos como utilizamos parâmetros com o ADO.Net. Para isso, criamos um array de objetos SqlParameter que representa os parâmetros existentes numa sentença executada no banco de dados, como, por exemplo, Stored Procedure. No entanto, isto não significa que ele só possa ser utilizado com esses objetos de banco de dados, já que a verificação desse quesito é realizada quando definimos a propriedade CommandType do objeto Command. Se esta for definida com o valor CommandType.Text, será verificado dentro da sentença SQL informada na propriedade CommandText do objeto Command que deverá ficar assim: “SELECT id, Assunto FROM Activity WHERE id = @id” ou “SELECT id, Assunto FROM Activity WHERE id = ?”. O primeiro exemplo é usado com objetos SqlClient e o segundo para uso com Odbc ou OleDb.

Listagem 4: Implentação do método Salvar na classe Activity

Public Function Salvar(ByVal isNew As Boolean) As Boolean

  Dim b As Boolean = False

  Dim s As String = "up_InsertActivity"

  Dim data As New ActivityManager.Data

  Dim parameters(10) As SqlClient.SqlParameter

  parameters(0) = New  SqlClient.SqlParameter("@assunto", Me._assunto)

  parameters(1) = New  SqlClient.SqlParameter("@dataInicio", Me._dataInicio)

  parameters(2) = New  SqlClient.SqlParameter("@dataTerminoPrevista", Me._dataTerminoPrevista)

  parameters(3) = New  SqlClient.SqlParameter("@lembrete", Me._lembrete)

  parameters(4) = New  SqlClient.SqlParameter("@descricao", Me._descricao)

  parameters(5) = New SqlClient.SqlParameter("@trabalhoPrevisto", Me._trabalhoPrevisto)

  parameters(6) = New SqlClient.SqlParameter("@trabalhoExecutado", Me._trabalhoExecutado)

  parameters(7) = New SqlClient.SqlParameter("@status", Convert.ToInt16(Me._status))

  parameters(8) = New SqlClient.SqlParameter("@activityColor", Convert.ToInt16(Me._corAtividade))

  parameters(9) = New SqlClient.SqlParameter("@priority", Convert.ToInt16(Me._prioridade))

  If (isNew) Then

     parameters(10) = New SqlClient.SqlParameter

     parameters(10).ParameterName = "@returnValue"

     parameters(10).SqlDbType = SqlDbType.Int

     parameters(10).Direction = _

        ParameterDirection.Output

  Else

     s = "up_UpdateActivity"

     parameters(10) = _

     New SqlClient.SqlParameter("@id", Me._id)

  End If

  Try

     data.ExecuteNonQuery(s, CommandType.StoredProcedure, parameters)

     Me._id = CType(parameters(10).Value, Int32)

     If (Me._id > 0) Then b = True

  Catch ex As DataException

     ...

  End Try

  Return b

End Function

 

Por padrão, todos os parâmetros possuem direção de entrada de dados (Input) e a propriedade que define este comportamento não é configurada. Porém, neste exemplo existe a possibilidade de salvar um novo objeto no banco de dados e, desta forma, necessitaremos do seu identificador, que é retornado pelo valor @@IDENTITY do SQL Server. Desse modo, definimos o parâmetro @returnValue e, na propriedade Direction, definimos o valor como ParameterDirection.Output. Esta propriedade possui ainda outras opções: Input-Output e ReturnValue, as quais são usadas, respectivamente, para parâmetros bidirecionais e para valores de retorno resultantes do termo Return usado em Stored Procedures. Nesse caso, o parâmetro não deve ser definido na mesma Stored Procedure, pois ocorrerá um erro.

As coleções ActivityCollection e AttachmentCollection são implementações das interfaces ICollection, IList e IEnumerable. Elas são um bom exemplo de como criar coleções fortemente tipadas com a tecnologia .Net. Em nossa aplicação, elas têm a função de armazenar os objetos em memória, evitando assim acessos desnecessários ao banco de dados.

Ambas as coleções possuem a implementação do método privado Carregar(), que tem a finalidade de recuperar os registros existentes e disponibilizá-los para a aplicação. A Listagem 5 apresenta a implementação desse método na classe ActivityCollection. Assim como fazemos com os demais métodos que acessam o banco de dados, realizamos a chamada a partir de um dos métodos disponibilizados pela classe Data. Atribuimos à variavel dr o valor retornado pelo método ExecuteReader e, em seguida, verificamos se existem registros, usando a propriedade Read do objeto SqlDataReader. Esse método é responsável pelo movimento do cursor nos registros encontrados, sempre para frente. Se o DataReader pode ser comparado ao antigo RecordSet Forward-Only/Read-Only, o método Read pode ser comparado ao método MoveNext. Ele sempre retornará verdadeiro se houver um próximo registro, o que nos permite retirar cada um dos valores existentes nele. Por fim, realizamos a leitura do registro e fechamos o DataReader através do seu método Close.

Listagem 5: Implementação do método Carregar() da classe ActivityCollection

Private Sub Carregar()

 Try

   Me.Clear()

   Dim data As New ActivityManager.Data

   Dim dr As SqlClient.SqlDataReader = _

   data.ReturnReader("up_LoadActivities", _

   CommandType.StoredProcedure)

   With dr

    While (.Read)

     Dim a As New Activity(.GetSqlInt32(0).Value, _

     .GetSqlString(1).Value, _

     .GetSqlDateTime(2).Value, _

     .GetSqlDateTime(3).Value)

     If Not (.GetSqlDateTime(4).IsNull) Then _

        a.DataTermino = .GetSqlDateTime(4).Value

     If Not (.GetSqlDateTime(5).IsNull) Then _

        a.Lembrete = .GetSqlDateTime(5).Value

     If Not (.GetSqlString(6).IsNull) Then _

        a.Descricao = .GetSqlString(6).Value

     a.TrabalhoExecutado = .GetSqlInt32(7).Value

     a.TrabalhoPrevisto = .GetSqlInt32(8).Value

     a.Status = CType(.GetSqlInt16(9).Value, _

                ActivityManager.Status)

     a.CorAtividade=CType(.GetSqlInt16(10).Value, _

       ActivityManager.ActivityColor)

     a.Prioridade = CType(.GetSqlInt16(11).Value, _

       ActivityManager.Priority)

     Call Me.Add(a)

    End While

    .Close()

   End With

 Catch ex As DataException

 ...

 End Try

End Sub

 

A classe Configuration é responsável pelo gerenciamento das configurações da aplicação armazenadas num arquivo de extensão .Config, que são de acordo com as definições do .Net Framework, arquivos XML que podem ser alterados conforme a necessidade da aplicação. Atualmente, o .Net só permite ler esses arquivos através do namespace System.Configuration. Outro ponto importante: os arquivos de configuração só podem ser usados em aplicações hospedadas em arquivos executáveis, aplicações ASP.Net e aplicações hospedadas no Internet Explorer. Assim sendo, todas as operações realizadas pela classe Configuration no arquivo .config da aplicação com o intuito de ler ou escrever no mesmo foram desenvolvidos através do uso do namespace System.Xml. Essas operações são definidas na classe Configuration através da implementação dos métodos Carregar(), que lê o arquivo .config, e CriarArquivoConfiguração(), que escreve o arquivo no disco. A Listagem 6 mostra como está implementado o método Carregar:

Listagem 6: Implementação do método Carregar da classe Configuration

Private Sub Carregar()

 Try

   If Not (Me.ProcuraArquivoConfiguracao) Then _

Me.CriarArquivoConfiguracao("ActivityManager.exe.config")

   Dim xr As New XmlTextReader(Environment.CurrentDirectory & "\" & Me._arquivoConfig)

   xr.WhitespaceHandling = WhitespaceHandling.None

   Dim key As String

   Dim value As String

   Do While xr.Read()

      If ((xr.HasAttributes) AndAlso _

         (xr.Name = "add")) Then

         key = xr.GetAttribute(0)

         value = xr.GetAttribute(1)

         Select Case key

           Case "LembretePadrao"

                Me._lembretePadrao = value

           Case "NomeBancoDados"

                Me._nomeBancoDados = value

           Case "NomeMaquina"

                Me._nomeMaquina = value

           Case "UsuarioBancoDados"

                Me._usuarioBancoDados = value

           Case "SenhaBancoDados"

                Me._senhaBancoDados = value

         End Select

      End If

   Loop

   xr.Close()

 Catch ex As XmlException

 ...

 End Try

End Sub

 

O método Carregar() utiliza a classe XmlTextReader para abrir um documento Xml (no caso, um arquivo .config) e permitir-nos sua leitura, a fim de verificamos o conteúdo existente nos diversos elementos e atributos. Note que, no geral, a maneira como realizamos a leitura do arquivo Xml através do objeto XmlTextReader é muito semelhante à que utilizamos para ler o banco de dados com o objeto DataReader, conforme mostrado na Listagem 5 (implementação do método Carregar() da classe ActivityCollection). Após a leitura, o arquivo é fechado através do método Close() e os recursos alocados são liberados.

Observe que, a qualquer momento, podemos alterar o banco de dados de nossa aplicação e migrar, por exemplo, do MS SQL Server para o MS Access, com baixíssimo impacto. Para fazer isso, basta mantermos o modelo ER (Entidade-Relacionamento) e alterarmos a classes de acesso a dados.

 

Definindo a User Interface

A última fase de implementação da aplicação é a User Interface. O ponto importante na sua implementação é que a inteligência existente nela só está relacionada à apresentação das informações ao usuário. Nosso aplicativo conta com 3 formulários:

·         frmMain – consiste no formulário principal da aplicação, onde são apresentadas todas as atividades existentes no banco de dados, de acordo com uma opção escolhida pelo usuário;

·         frmNew – onde o usuário pode cadastrar uma nova atividade;

·         frmOption – onde o usuário pode definir as configurações da aplicação.

 

A Tabela 1 apresenta os principais objetos com propriedades alteradas utilizados na construção do formulário frmMain, apresentado na Figura 2 (o principal formulário da aplicação):

 

Tabela 1: Controles existentes no formulário frmMain

ListView (ID)

lsvPrincipal

   Activation

OneClick

   Alignment

SnapToGrid

   AllowColumnReorder

True

   CheckBoxes

True

   ContextMenu

ctmList

   Dock

Fill

   MultiSelect

False

   SmallImageList

imlPrincipal

   View

Details

Button1 (ID)

btnNovaAtividade

   FlatStyle

Popup

   Text

&Nova Atividade

Label1(ID)

lblVisaoAtual

   Text  

Visão Atual

ListBox(ID)

lbxView

StatusBar(ID)

stbPrincipal

LinkLabel1(ID)

llbOpcao

   Text

Opções...

LinkLabel2(ID)

llbSair

   Text

Sair

NotifyIcon(ID)

nfiActivity

   ContextMenu

ctmPrincipal

   Text

Gerenciador de atividades

ContextMenu1(ID)

ctmPrincipal

ContextMenu2(ID)

ctmList

ImageList(ID)

imlPrincipal

 

O formulário principal (Figura 2) tem por objetivo a visualização da ListView, na qual listamos as atividades armazenadas no banco de dados.  O preenchimento da ListView ocorre através do método frmMain.MontaLista(), onde verificamos as atividades existentes na coleção ActivityCollection adicionadas à lista. Uma alternativa ao uso do objeto ListView é o Datagrid. Não utilizaremos essa opção aqui porque nosso objetivo é apresentar apenas os dados e o Datagrid é um objeto mais complexo, o que resultaria num consumo maior de recursos da aplicação. O Datagrid é indicado nas situações onde é necessário manipular as informações diretamente na lista associada ao DataSet, o que não é o caso.

 

image004.jpg

Figura 2 Formulário principal (frmMain) e de configurações (frmOption) do Gerenciador de atividades

 

O formulário frmNew mostrado na Figura 3 permite manipular os objetos Activity, AttachmentCollection e Attachment.  Para melhor acoplamento com o objeto Activity, fazemos uma sobrecarga do construtor e recebemos como argumento um objeto Activity. Essa sobrecarga visa permitir a abertura de uma atividade através do formulário principal e o preenchimento dos campos por meio da atribuição dos valores das propriedades do objeto informado aos objetos de UI do formulário. A Tabela 2 apresenta os principais objetos utilizados para a construção do formulário frmNew:

 

Tabela 2: Controles existentes no formulário frmNew

TabControl(ID)

TbcNew

Button1 (ID)

BtnOk

   Text

OK

Button2 (ID)

BtnCancelar

   Text

Cancelar

Button3 (ID)

BtnAnexo

   Text

...

TextBox1 (ID)

TxtAssunto

TextBox2 (ID)

TxtTrabalhoPrevisto

TextBox3 (ID)

TxtTrabalhoExecutado

TextBox4 (ID)

TxtDescricao

   Multiline

True

   ScrollBars

Horizontal

DateTimePicker1 (ID)

DptDataInicio

   Format

Short

DateTimePicker2 (ID)

DptDataTermino

   Format

Short

DateTimePicker3 (ID)

DptLembrete

   CustomFormat

ddd dd/MM/yyyy

   Format

Custom

ComboBox1 (ID)

CmbStatus

ComboBox2 (ID)

CmbPrioridade

ComboBox3 (ID)

CmbLembrete

   Text

None

ComboBox4 (ID)

CmbCorAtividade

CheckBox (ID)

CkbLembrete

   Text

Lembrete:

ListBox (ID)

LbxAnexo

   BackColor

Control

   BorderStyle

FixedSingle

 

Para realizar a persistência dos dados, execute o método Salvar do objeto Activity, apresentado na Listagem 7. Para isso, use a implementação no evento Click do botão OK existente no formulário frmNew (Figura 3):

Listagem 7: Implementação do evento Click do botão btnOk

Private Sub btnOk_Click(ByVal sender As Object, _

  ByVal e As System.EventArgs) Handles btnOk.Click

  If (Me._activity Is Nothing) Then

    Me._activity = New ActivityManager.Activity( _

    Me.txtAssunto.Text, _

    DateTime.Parse(Me.dtpDataInicio.Text), _
    DateTime.Parse(Me.dtpDataTermino.Text))

  Else

    Me._activity.Assunto = Me.txtAssunto.Text

    Me._activity.DataInicio = _

    DateTime.Parse(Me.dtpDataInicio.Text)

    Me._activity.DataTerminoPrevista = _

    DateTime.Parse(Me.dtpDataTermino.Text)

  End If

  With Me._activity

    .Descricao = Me.txtDescricao.Text

    .TrabalhoExecutado = _

    Int32.Parse(Me.txtTrabalhoExecutado.Text)

    .TrabalhoPrevisto = _

    Int32.Parse(Me.txtTrabalhoPrevisto.Text)

    .Lembrete = Nothing

    If (Me.ckbLembrete.Checked) Then

      Dim dt As New DateTime( _

      Me.dtpLembrete.Value.Year, _

      Me.dtpLembrete.Value.Month, _

      Me.dtpLembrete.Value.Day, _

      Me.cmbLembrete.Text.Substring(0, 2), _

      Me.cmbLembrete.Text.Substring(3), 0)

      .Lembrete = dt

    End If

    .Status = CType(Me.cmbStatus.SelectedIndex, _

        ActivityManager.Status)

    .Prioridade = _

        CType(cmbPrioridade.SelectedIndex, _

        ActivityManager.Priority)

    .CorAtividade = _

        CType(cmbCorAtividade.SelectedIndex, _

        ActivityManager.ActivityColor)

    Dim b As Boolean = True

    If (.Id > 0) Then b = False

    If (.Salvar(b)) Then

       Me.ShowMessage("Atividade salva com sucesso!")

       Me.Close()

    End If

  End With

End Sub

 

É função da UI atribuir as informações à classe de negócios, que, por sua vez, deve realizar a validação dos dados. Neste ponto, surge a grande dúvida: o que deve ficar na classe de negócio, e o que deve ficar na UI? Por exemplo: o objeto Activity possui a propriedade TrabalhoExecutado, recebendo e atribuindo um tipo Integer. Porém, o valor é passado através de uma informação vinda de um tipo String, que é a propriedade Text do objeto Textbox. Onde deve ser tratada a validação, na UI ou na classe Activity? A resposta correta é: depende! Pode ser no objeto de negócio, pode ser na UI, pode ser em ambos. Depende do grau de abstração e acoplamento que desejamos em nossa aplicação. Naturalmente, a terceira opção é a mais indicada, pois fornece ao mesmo tempo baixo acoplamento (o que reduz os “round-trips” entre o objeto de negócio e a UI) e mais segurança durante a execução da aplicação.

 

 

image006.jpg

Figura 3 Form frmNew, para inclusão de novas atividades

 

O formulário para configuração do sistema (Figura 2), frmOption, tem por objetivo permitir que o usuário possa realizar pequenas alterações nas configurações padrão do sistema, como a conexão com o banco de dados. A Tabela 3 apresenta os objetos usados no formulário frmOption:

 

Tabela 3: Controles existentes no formulário frmOption

TabControl(ID)

tbcConfig

Button1 (ID)

btnOk

   Text

OK

Button2 (ID)

btnCancelar

   Text

Cancelar

TextBox1 (ID)

txtArquivoConfiguracao

TextBox2 (ID)

txtBancoDados

TextBox3 (ID)

txtNomeMaquina

TextBox4 (ID)

txtUsuario

TextBox5 (ID)

txtSenha

   PasswordChar

*

ComboBox (ID)

cmbLembrete

   DropDownStyle

DropDownList

CheckBox (ID)

chkSeguranca

   Checked

True

   CheckState

Checked

   Text

Usar segurança integrada

 

 A Listagem 8 mostra como os dados são carregados de um objeto para o formulário para, então, serem manipulados pelo usuário. Neste caso, use uma instância da classe Configuration para o frmOption através do método Carregar() (chamado pelo construtor do formulário):

Listagem 8: Método Carregar do frmOption

Private Sub Carregar()

  Try

    Me._config = New ActivityManager.Configuration(Me.txtArquivoConfiguracao.Text)

    With Me._config

      Me.txtArquivoConfiguracao.Text = _

        .ArquivoConfiguracao

      Me.txtBancoDados.Text = .NomeBancoDados

      Me.txtUsuario.Text = .UsuarioBancoDados

      Me.cmbLembrete.Text = .Lembrete

      Me.txtNomeMaquina.Text = .NomeMaquina

    End With

  Catch ex As ActivityException

      Me.ShowMessage(ex.Message)

  Catch ex As Exception

      Me.ShowMessage(ex.Message)

  End Try

End Sub

 

Observe que os dados contidos em cada uma das propriedades existentes na classe Configuration são passados para os objetos de UI existentes no frmOption, o que permite que os usuários realizem as devidas modificações e depois as salvem clicando no botão Ok existente. Assim, os dados são retornados para a classe Configuration que, como já vimos, faz a manipulação do arquivo de configuração da aplicação (arquivo com extensão .config), persistindo os dados nele e dispensando o acesso ao banco de dados.

 

Conclusão

Nosso sistema agora está formado. Planejamos, revisamos, geramos os esqueletos de código e, por fim, adicionamos a inteligência necessária para a sua utilização. O sistema foi projetado para ser executado numa aplicação Windows e para informar ao usuário de suas atividades, o que significa que é necessário manter as informações sempre disponíveis. Dentro deste conceito, definimos um modelo de objetos que permite o acesso contínuo e rápido às informações. Além disso, a aplicação está sempre na memória.

Por fim, implementamos todas as camadas do sistema de acordo com um modelo distribuído. Nosso sistema possui uma camada de acesso a dados, uma camada de objetos de negócio e a camada de apresentação, além do banco de dados, onde possuímos também inteligência implementada.

No próximo artigo, definiremos o deployment do sistema e os recursos necessários para instalar automaticamente o banco de dados e a aplicação.