fevereiro 2012
ndice
Editorial
Chegou fevereiro e com ele a nossa maior festa popular. O Carnaval. Ento vamos todos aproveitar e festejar bastante! Sem se esquecer ...
Delphi
Delphi
04 14
05 28
08 30
03
Delphi
Android - Criando uma tela de Venda
Delphi
Conexo ao Firebird em Lazarus
22
26
Dicas
- Dicas Delphi
Legenda
Iniciante Intermedirio Avanado
fevereiro 2012
Bem-vindo
Chegou fevereiro e com ele chega nossa maior festa popular. O Carnaval. Ento vamos todos aproveitar e festejar bastante! Sem se esquecer que depois da folia o trabalho prossegue. Ento nada melhor que ler os artigos desse ms para que a volta ao batente seja bem produtiva. Para comear temos o artigo de nosso consultor Eduardo Massud mostrando como manipular arquivos no Delphi utilizando o recurso Drag and Drop. Na sequncia temos o artigo Criando uma aplicao Client Server no Delphi parte 4 onde nosso colaborador Luciano Pimenta segue mostrando como desenvolver uma aplicao Delphi baseada na robustez da arquitetura Client Server. Para quem pretende se aventurar pela plataforma Android, nosso colaborador Thiago Montebugnoli traz esse ms como criar uma tela de venda nesse plataforma. Para finalizar, sigo mostrando a vocs a ferramenta Lazarus. Esse ms trago o artigo Conectando Firebird ao Lazarus, onde demonstro como manipular bases de dados Firebird em Lazarus. isso. Bom carnaval a todos e at maro !
Av. Prof Celso Ferreira da Silva, 190 Jd. Europa - Avar - SP - CEP 18.707-150 Informaes e Suporte: (14) 3732-1529 http://www.theclub.com.br Cadastro: cadastro@theclub.com.br Suporte: suporte@theclub.com.br Informaes: info@theclub.com.br Skype Cadastro: theclub_cadastro Skype Suporte: theclub_linha1 theclub_linha2 theclub_linha3
Internet
www.twitter.com/theclubbr
Copyright The Club Megazine 2009 Diretor Tcnico Marcos Csar Silva Diagramao Eduardo Massud Capa Vitor Manuel Rodrigues Reviso Eliziane Valentim Colunistas Antonio Spitaleri Neto Eduardo Massud Leonora Golin Luciano Pimenta Thiago Cavalheiro Montebugnoli Impresso e acabamento:
GRIL - Grfica e Editora Taquarituba-SP - Tel. (14) 3762-1345
Reproduo
A utilizao, reproduo, apropriao, armazenamento em banco de dados, sob qualquer forma ou meio, de textos, fotos e outras criaes intelectuais em cada publicao da revista The Club Megazine so terminantemente proibidos sem autorizao escrita dos titulares dos direitos autorais. Delphi marca registrada da Borland International, as demais marcas citadas so registradas pelos seus respectivos proprietrios.
Delphi
Ler e exibir o contedo de vrios arquivos de texto utilizando o sistema de arrastar e soltar (Drag Drop).
A funo de arrastar e soltar tem tido grande viabilidade, por ser bem mais intuitiva de que a busca com a caixa de dilogo, utilizando um mtodo muito mais simples e eficaz que agiliza no reconhecimento dos arquivos, interagindo e dinamizando nos modos de integrao do aplicativo desenvolvido com o Sistema Operacional Ao criarmos a unit ser implementada a procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES; ela a responsvel por interpretar e gerenciar todo arquivo que arrastado para a aplicao.
Graphics, Controls, Forms, Dialogs, ComCtrls, StdCtrls, Menus, ExtCtrls, Grids, DBGrids, DB, DBClient, OleCtrls, SHDocVw; type TForm1 = class(TForm) Memo1: TMemo; StatusBar1: TStatusBar; OpenDialog1: TOpenDialog; ListBox1: TListBox; DataSource1: TDataSource; WebBrowser1: TWebBrowser; procedure Open1Click(Sender:
TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Close1Click(Sender: TObject); procedure ListBox1Click(Sender: TObject); procedure ListBox1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); private function ArquivoSuportado(const Extensao: string):
fevereiro 2012
05
Boolean; procedure Incluir(const Arquivo: string); procedure Exibir(I: Integer); procedure WMDropFiles(var Msg: TWMDropFiles); message WM_ DROPFILES; public { Public declarations } end; var Form1: TForm1; implementation
Ao fecharmos o executvel, removemos todos os arquivos listados e abertos pelo ListBox, limpando-o
procedure TForm1. FormDestroy(Sender: TObject); begin DragAcceptFiles(Self. Handle, False); end; procedure TForm1. WMDropFiles(var Msg: TWMDropFiles); var I: Integer; Catcher: TFileCatcher; begin inherited; Catcher := TFileCatcher. Create(Msg.Drop); try for I := 0 to Pred(Catcher.FileCount) do Incluir(Catcher. Files[I]); finally Catcher.Free; end; Msg.Result := 0; end; procedure TForm1. Incluir(const Arquivo: string); begin procedure TForm1. Close1Click(Sender: TObject); var ItemIdx: Integer; begin ItemIdx := ListBox1. ItemIndex; if ItemIdx > -1 then begin ListBox1.Items. Delete(ItemIdx); if ItemIdx >= ListBox1.Items.Count then Dec(ItemIdx); Exibir(ItemIdx); end; end;
Na clausula Uses da seo implementation devemos declarar as funes ShellAPI e UFileCatcher que sero responsveis por reconhecer o arquivo recebido.
Ser includa tambm uma constante com o nome de Extenses, que receber todas as extenses que podero permitir que os arquivos possam ser interpretados ao serem selecionados
Na procedure Exibir carregamos todos os arquivos para os componentes que iro permitir que sejam visualizados
Para que possamos habilitar o sistema de arrastar e soltar devemos criar 3 procedimentos que sero de inicializao, finalizao e do momento em que estes arquivos so transferidos, e outro
06
fevereiro 2012
if ArquivoSuportado(Arquivo) then
procedure TForm1.Exibir(I: Integer); var FileName: string; begin if I >= 0 then begin FileName := ListBox1. Items[I]; ListBox1.ItemIndex := I; Memo1.Lines.
LoadFromFile(FileName); WebBrowser1. Navigate(FileName); StatusBar1.SimpleText := FileName; end else begin Memo1.Clear; StatusBar1.SimpleText := 'Nenhum arquivo selecionado!'; end; end; function TForm1. ArquivoSuportado(const Extensao: string): Boolean; begin Result := Pos('*' + ExtractFileExt(Extensao) + ';', Extensoes) > 0; end;
Mesmo se tratando de um component Drag Drop, o Delphi executar algumas funes necessrias para que o arquivo seja recebido, como a listagem de registros ao selecionar o listbox e a utilizao do componente OpenDialog, que ir reconhecer o caminho daquele arquivo.
procedure TForm1. ListBox1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin try if Key = VK_DELETE Then ListBox1.Items. Delete(ListBox1. ItemIndex); Memo1.Clear; WebBrowser1.
Veja a Figura 1
Concluso
O Sistema de arrastar e soltar sempre trouxe muito mais agilidade e simplicidade aos aplicativos por ser um mtodo mais intuitivo ao usurio.
procedure TForm1. ListBox1Click(Sender: TObject); begin Exibir(ListBox1. ItemIndex); end; procedure TForm1. Open1Click(Sender: TObject); begin if (OpenDialog1.Execute) then Incluir(OpenDialog1. FileName); end;
Sobre o autor
Eduardo Massud
Consultor Tcnico The Club.
eduardo@theclub.com.br
fevereiro 2012
07
CREATE PROCEDURE SP_ORDEM_ SERVICO @nCdVeiculo int, @nCdOrcamento int, @sDsObservacoes varchar(max), @nCdOrdem int output AS BEGIN INSERT INTO OS (nCdVeiculo, nCdOrcamento, tDtOrdem, sDsObservacoes) VALUES (@nCdVeiculo, @ nCdOrcamento, getdate(), @ sDsObservacoes) select @nCdOrdem = @@identity from OS; END CREATE PROCEDURE SP_ORDEM_ SERVICO_DETALHES @nCdOrdem int, @nCdPeca int, @nQtPeca numeric(9,2), @nVlUnitario numeric(9,2) AS BEGIN INSERT INTO OS_DETALHE (nCdOrdem, nCdPeca, nQtPeca, nVlUnitario) VALUES (@nCdOrdem, @ nCdPeca, @nQtPeca, @ nVlUnitario) END Figura 1. Tela de cadastro da OS.
que os itens esto vinculados. Tudo isso, dentro de uma transao, feita pela aplicao. No podemos ter itens da OS inseridos, sem a vinculao do cdigo da OS.
Criando o cadastro
Vamos criar agora, o cadastro da nossa OS. Veja na Figura 1 a tela de OS da aplicao. No Click do boto, usamos o cdigo da Listagem 2 para pesquisar os dados do oramento. Listagem 2. Pesquisa de Oramento pela placa
DM.cdsOrcamentoPlaca. Close; DM.cdsOrcamentoPlaca. Params[0].AsString := edtPlaca.Text; DM.cdsOrcamentoPlaca.Open; if DM.cdsOrcamentoPlaca. RecordCount > 1 then begin try frmPesquisa := TfrmPesquisa.Create(self); frmPesquisa.dsPesquisa. DataSet :=
DM.cdsOrcamentoPlaca; frmPesquisa.Descricao := 'Nome da pea/servio'; frmPesquisa. ShowModal; finally frmPesquisa.Free; end; end; if (DM.cdsOrcamentoPlaca. RecordCount > 1) or (DM.cdsOrcamentoPlaca. RecordCount = 1) then begin nCdOrcamento := DM.cdsO rcamentoPlacanCdOrcamento. AsInteger; nCdVeiculo := DM.cdsOrcamentonCdVeiculo. AsInteger; end;
Note que na SP_ORDEM_SERVICO, temos no final um cdigo para selecionar o valor inserido no campo chave (nCdOrdem) da tabela OS, que ser retornado nos parmetros (do tipo output) da Stored Procedure. Assim, com o retorno desse valor, podemos indicar para a SP_ORDEM_SERVICO_DETALHES que vai inserir os itens (peas/servios), o cdigo da OS
Temos a mesma funcionalidade anterior, caso a consulta retorne mais de um registro, mostramos a tela de pesquisa genrica para que o usurio possa escolher o respectivo oramento. Veja que no final
fevereiro 2012
09
do cdigo, preenchemos variveis auxiliares com o cdigo do oramento e veculo para serem usadas na confirmao da OS. O comando SQL do cdsOrcamentoPlaca que preenche os dados do oramento:
select O.nCdOrcamento, O.nCdVeiculo, O.sNmCliente, O.sDsParecer, V.sNmModelo from ORCAMENTO O INNER JOIN VEICULO V ON V.nCdVeiculo = O.nCdVeiculo WHERE V.sNrPlaca = :PLACA and O.nIdStatus = 1
Um detalhe importante, veja que estamos pesquisando somente nos oramentos ativos (nIdStatus = 1), para que a pesquisa seja correta e no traga oramentos que j foram usados no veculo. Usando a pesquisa genrica no precisamos criar outra tela de consulta, ganhamos assim produtividade em nossa aplicao.
Adicionando peas/servios na OS
Precisamos adicionar em memria as peas/ servios escolhidos pelo usurio. No seria correto, ficar adicionando e excluindo os itens no banco de dados. Seria uma degradao de performance muito grande. Vamos adicionar um ClientDataSet no formulrio que ter os seguintes campos: cdigo da pea, nome da pea, quantidade e preo. Veja na Figura 3 os campos do ClientDataSet. Figura 5. Menu de contexto no grid Criamos um campo calculado para saber o subtotal da pea (quantidade * preo). Adicione um DataSource no formulrio e faa a ligao do mesmo com o ClientDataSet e tambm do DBGrid com o DataSource. Configure os campos do ClientDataSet no Grid. Veja na Figura 4 como ficou nosso formulrio. Vamos agora implementar o mtodo responsvel por adicionar os itens em memria no ClientDataSet. Veja na Listagem 3, o mtodo IncluirItem. Listagem 3. Incluindo o item em memria
procedure TfrmOrdem. IncluirItem; begin //validaes if edtPeca.Text = '' then MessageDlg('Campo Pea/servio: preenchimento obrigatrio.', mtError, [mbNo], 0) else if (edtQuantidade. Text = '') then MessageDlg('Campo Quantidade: preenchimento obrigatrio.', mtError, [mbNo], 0) else if edtPreco.Text = '' then MessageDlg('Campo Preo: preenchimento obrigatrio.', mtError, [mbNo], 0) else begin //verifica se ClientDataSet ativo
10
fevereiro 2012
Grid para que fique dentro do GroupBox, assim o mesmo tambm fica desabilitado. Voc tambm pode desabilitar o campo Observaes e o boto de confirmar. Uma melhoria bem legal que poderamos fazer, mas deixo para voc exercitar, seria o de trazer o valor da pea/servio cadastrado no banco no campo Preo. Usaramos apenas como sugesto, pois o usurio poderia modificar o valor. Vamos agora, confirmar nosso oramento.
Figura 6. Cadastro de oramento em execuo ficamos se o ClientDataSet no esta ativo, assim, criamos o mesmo em memria usando o mtodo CreateDataSet. Para inserir um registro no controle, chamamos o Insert, preenchemos os campos e por fim, executamos o mtodo Post. Para excluir um item, vamos usar um menu de contexto. Adicione um PopupMenu no formulrio. Crie um item de menu chamado Excluir. No evento OnClick do menu, digite o seguinte cdigo:
if not cdsItens.Active then cdsItens. CreateDataSet; //inclui o item cdsItens.Insert; cdsItensnCdPeca. AsInteger := nCdPeca; cdsItenssNmPeca. AsString := edtPeca.Text; cdsItensnQtde. AsInteger := StrToInt(edtQuantidade. Text); cdsItensnVlPeca.AsCurrency := StrToCurr(edtPreco. Text); cdsItens.Post; //limpa campos edtPeca.Text := ''; edtQuantidade.Text := ''; edtPreco.Text := ''; end; end;
Estamos verificando se o ClientDataSet esta ativo, ento chamamos o Delete para excluir o item da memria. Precisamos apenas vincular o controle com a propriedade PopupMenu do grid. Veja na Figura 5 o uso do menu de contexto no Grid. Para finalizarmos o nosso cadastro, vamos implementar algumas regras na tela para que o processo seja executado sem falhas. Vamos desabilitar (propriedade Enabled) o GroupBox com os dados do item para quando o usurio abrir o formulrio, no possa adicionar itens, sem antes pesquisar um oramento. Vamos habilitar o mesmo somente quando for encontrado um oramento. Por isso, modifiquei o
Veja que fizemos algumas validaes simples, antes de inserir o item. Poderamos ter mais validaes, como valor e quantidade maior que zero etc. A insero do item, simples, primeiro veri-
11
Caso tenha alguma dvida sugiro uma pesquisa para entender o funcionamento de transao no dbExpress. Nota: a partir da verso 2010 do Delphi, essa a implementao para utilizao de transao no dbExpress. Em verses anteriores, a lgica a mesma, apenas muda as classes e mtodos.
spOS.Params[0].AsInteger := nCdVeiculo; spOS.Params[1]. AsInteger := nCdOrcamento; spOS.Params[2]. AsString := sDsObservacoes; spOS.ExecProc; //armazena o valor inserido no banco nCdOrdem := spOS. Params[3].AsInteger; //percorre os itens e insere no banco for i := 0 to cdsItens. RecordCount - 1 do begin //insere os itens spOS_Itens. Params[0].AsInteger := nCdOrdem; spOS_Itens. Params[1].AsInteger := cdsItens. FieldByName('nCdPeca'). AsInteger; spOS_Itens. Params[2].AsInteger := cdsItens. FieldByName('nQtde'). AsInteger; spOS_Itens. Params[3].AsCurrency := cdsItens. FieldByName('nVlPeca'). AsInteger; spOS_Itens. ExecProc; end; bRetorno := true; except //caso ocorra algum
erro, da roolback SysCar.RollbackInco mpleteFreeAndNil(TransDe sc); end; //comita as alteraes no banco SysCar. ommitFreeAndNil(TransDesc); Result := bRetorno; end;
Cadastrando uma OS
Para finalizar nosso cadastro, vamos codificar o Confirmar na tela de OS, para que chame o mtodo do Data Module, conforme a Listagem 5. Listagem 5. Chamando a insero da OS
if (not cdsItens.Active) or (cdsItens.RecordCount = 0) then MessageDlg(' necessrio incluir peas/servios para confirmar a Ordem de servio.', mtError, [mbNo], 0) else begin if DM.InserirOS(nCdOrcamento, nCdVeiculo, cdsItens, mmObservacoes.Text) then begin MessageDlg('Registro inserido com sucesso.', mtInformation, [mbNo], 0); dsOrcamento.DataSet. Close; cdsItens.Close; end else MessageDlg('Ocorreu um erro. Contate o suporte', mtError, [mbNo], 0) end;
O cdigo esta comentado, mas vamos entender seu funcionamento. Primeiro, declaramos algumas variveis para auxiliar-nos. A varivel do tipo TDBXTransaction, parmetro para o mtodo que inicia a transao. Dentro de um bloco try...except, configuramos os parmetros necessrios para a insero da OS e de seus itens. Algo bastante simples, como podemos perceber. A Stored Procedure que insere a OS, retorna o cdigo da mesma em seu ltimo parmetro. Assim, armazenamos esse valor para us-lo na insero dos itens da OS. O bloco except, ser executado, caso ocorra algum erro, assim, chamamos o RollbackIncompleteFreeAndNil para no efetivar nada no banco de dados. Se no ocorrer nenhum problema, o fluxo continuar, sem executar o roolback e assim, executar o CommitFreeAndNil, que a insero dos dados, efetivamente, no banco. Veja que criamos uma function, que retorna um boolean, para sabermos se a insero foi realizada corretamente. Usamos uma varivel, que preenchida como false, pois se ocorrer erro, o retorno dever ser falso. Preenchemos a varivel com true, somente se o fluxo ocorreu corretamente e no gerou erro.
Veja que validamos se o ClientDataSet esta ativo ou se existem itens cadastrados. Em caso negativo, emitimos uma mensagem, seno, chamamos a funo para inserir. Assim, verificamos se a mesma retornou com sucesso, para emitir a devida mensagem.
12
fevereiro 2012
Poderamos emitir a mensagem retornada para o usurio. Fica a seu critrio implementar isso. Execute o projeto e faa um teste (Figura 8). Poderamos incrementar nosso cadastro, validando os campos Quantidade e Preo. Caso voc deseje alterar a ordem de servio, teramos que rever a parte do carregamento de itens, onde apenas precisaramos preencher o cdsItens com as peas/servios cadastrado no banco. Teramos que ter tambm, obviamente, uma pesquisa para que o usurio possa escolher a ordem de servio que deseja alterar. Para finalizar completamente, aps confirmar, precisamos mudar o status do oramento vinculado a OS. Caso voc deseje implementar a funcionalidade de editar os itens da OS, voc deve atentar para mudar a lgica referente a status do Oramento e da OS. Listagem 6. Alterando o status do Oramento Data module
procedure TDM.AlterarStat usOrcamento(nCdOrcamento: integer); var sql: string; Query: TSQLQuery; begin sql := 'update ORCAMENTO set nIdStatus = 0 where nCdOrcamento = ' + IntToStr(nCdOrcamento); Query := TSQLQuery. Create(nil); try Query.SQLConnection := SysCar; Query.SQL.Clear; Query.SQL.Add(sql); Query.ExecSQL(); finally Query.Free; end; end;
Figura 8. Salvando uma ordem de servio que se refere a cadastros, pois j implementamos os principais. Criamos os cadastros que podemos chamar de auxiliares (cliente, peas e veculos), pois eles abastecem os cadastros principais, bem como os cadastros que so os principais (oramento e ordem de servio) do nosso projeto. No prximo e ltimo artigo, vamos finalizar o projeto com a parte de relatrios, onde criaremos listagens, relatrios com parmetros etc. Um grande abrao a todos e at a prxima!
Sobre o autor
Luciano Pimenta
Tcnico em Processamento de Dados, desenvolvedor Delphi/C# para aplicaes Web com ASP.NET e Windows com Win32 e Windows Forms. Palestrante da 4 edio da Borland Conference (BorCon). Autor de mais de 60 artigos e de mais de 300 vdeos aulas publicadas em revistas e sites especializados. consultor da FP2 Tecnologia (www.fp2.com.br) onde ministra cursos de programao e banco de dados. desenvolvedor da Paradigma Web Bussiness em Florianpolis-SC.
Formulrio da OS
...
www.lucianopimenta.net
13
14
fevereiro 2012
schemas.android.com/apk/res/android android:orientation=vertical android:layout_width=fill_parent android:layout_height=fill_parent > <TextView android:layout_width=fill_parent android:layout_height=wrap_content android:text=@string/hello /> </LinearLayout>
Figura 02: Hierarquia de Componentes Utilizados. Bem, como este no nosso primeiro projeto, esta varivel hello no ser utilizada, portanto podemos exclu-la tanto de nossa tela de recursos quanto na tela principal. Figura 01: Recursos no Sistema Android.
Pergunta: O que seria a palavra @string na linha android:text=@ string/hello ? Resposta: uma referncia que temos no nosso arquivo de recursos strings.xml encontrado no caminho /res/values. Para entendermos melhor d um clique no mesmo e ver uma tela parecida com a Figura 01. Temos aqui uma tela de recursos para o Sistema Android, ou seja, podemos mudar nosso valor da varivel hello ou da app_name que seria o ttulo de nosso exemplo. Em Resources Elements temos vrios elementos que podemos trabalhar e explorar nos possibilitando a criao e manipulao de variveis muito teis ao decorrer de nossa necessidade. Neste mesmo artigo criaremos uma do tipo Array que iremos aprender um pouco mais adiante. importante tambm ter em mente que podemos realizar este trabalho por XML, veja o cdigo equivalente a Figura 01.
15
A tela de venda possuir a seguinte estrutura: o o o o Nome Cliente Cidade Estado Produtos The Club Lite (Delphi) The Club Lite (C#,.NET) The Club Professional The Club Student Boto Efetuar Compra
android:layout_width=match_parent android:id=@+id/linearLayout1 android:orientation=horizontal> <LinearLayout android:layout_ height=match_parent android:orientation=vertical android:layout_width=wrap_content android:id=@+id/linearLayout4> <TextView android:layout_ width=wrap_content android:id=@+id/textView3 android:layout_height=wrap_ content android:text=Cidade></TextView> <EditText android:id=@+id/ edtCidade android:layout_height=wrap_ content android:layout_width=206dp></ EditText> </LinearLayout> <LinearLayout android:layout_ height=match_parent android:orientation=vertical android:id=@+id/linearLayout3 android:layout_width=match_ parent> <TextView android:layout_ width=wrap_content android:id=@+id/textView2 android:layout_height=wrap_ content android:text=Estado></TextView> <Spinner android:id=@+id/ spnEstado android:entries=@array/uf android:layout_height=wrap_ content android:layout_width=match_ parent></Spinner> </LinearLayout> </LinearLayout> <TextView android:textAppearance=?and roid:attr/textAppearanceMedium android:id=@+id/textView4 android:layout_height=wrap_ content android:text=PRODUTOS android:layout_width=wrap_content
Para obtermos um resultado legal trabalharemos corretamente com os componentes de Layouts acima descritos. Por padro nosso projeto criado com um LinearLayout(Vertical). A descrio dos componentes utilizados e a disposio em nossa tela principal. Veja a Figura 02 para melhores detalhes. importante entender o funcionamento destes dois principais tipos de componentes de layouts, pois possvel manej-los da forma que desejar, colocando um dentro do outro, entre outros tipos de configuraes. Podemos conferir o cdigo XML correspondente abaixo:
<?xml version=1.0 encoding=utf-8?> <LinearLayout xmlns:android=http:// schemas.android.com/apk/res/android android:orientation=vertical android:layout_width=fill_parent android:layout_height=fill_parent> <LinearLayout android:layout_ height=wrap_content android:orientation=vertical android:layout_width=wrap_content android:id=@+id/linearLayout2> <TextView android:layout_ width=wrap_content android:text=Nome Cliente: android:id=@+id/textView1 android:layout_height=wrap_ content></TextView> <EditText android:layout_ width=316dp android:id=@+id/edtCliente android:layout_height=wrap_ content></EditText> </LinearLayout> <LinearLayout android:layout_ height=wrap_content
16
fevereiro 2012
android:layout_gravity=center></ TextView> <RadioGroup android:id=@+id/rgCursos xmlns:android=http://schemas. android.com/apk/res/android android:orientation=vertical android:layout_width=fill_parent android:layout_height=wrap_ content> <RadioButton android:id=@+id/ rbLiteDelphi android:layout_width=wrap_content android:layout_height=wrap_content android:text=The Club Lite (Delphi) - R$ 59,90></RadioButton> <RadioButton android:id=@+id/ rbLiteC android:layout_width=wrap_content android:layout_height=wrap_ content android:text=The Club Lite (C#,. NET) - R$ 59,90></RadioButton> <RadioButton android:id=@+id/ rbProfessional android:layout_width=wrap_content android:layout_height=wrap_ content android:text=The Club Professional - R$ 88,00></RadioButton> <RadioButton android:id=@+id/ rbStudent android:layout_width=wrap_content android:layout_height=wrap_ content android:text=The Club Student - R$ 25,00></RadioButton> </RadioGroup> <Button android:id=@+id/btnEfetuar android:layout_height=wrap_content android:layout_width=match_parent android:text=Efetuar Venda></Button> </LinearLayout>
O componente Spinner parecido com o Combobox do Delphi, ou seja, podemos armazenar uma lista de Strings nele. Neste caso armazenaremos via XML todos os estados do Brasil, para isto acesse o caminho \values\strings.xml e adicionaremos uma varivel do tipo Array. Podemos fazer isto via cdigo XML ou utilizando a interface grfica. Veja abaixo o XML correspondente:
<string-array name=uf> <item >AC </item> <item >AL </item> <item >AM </item> <item >BA </item> <item >CE </item> <item >ES </item> <item >GO </item> <item >MA </item> <item >MT </item> <item >MS </item> <item >MG </item> <item >PA </item> <item >PB </item> <item >PR </item> <item >PE </item> <item >PI </item> <item >RJ </item> <item >RN </item> <item >RS </item> <item >RO </item> <item >RR </item> <item >SC </item> <item >SP </item> <item >SE </item> <item >TO </item> </string-array>
Prontinho... Criamos agora mais um recurso no Android que poder ser utilizado em qualquer tela que precisarmos, no legal? Para carregarmos estes recursos utilizamos a funo entries, veja abaixo o XML completo deste componente. Podemos rodar o exemplo e teremos uma tela parecida com a da Figura 03
Devemos dar uma ateno especial no componente RadioButton em conjunto com o RadioGroup, ele nos possibilita a criao de um grupo que permite apenas selecionar um tipo de Produto, que no nosso caso especfico o que devemos fazer.
17
1 - The Club Lite (Delphi) 2 - The Club Lite (C#,.NET) 3 - The Club Professional 4 - The Club Student
Cdigo correspondente:
int curso; } Codificando o Boto Efetuar Compra O primeiro passo abrir o arquivo principal .java, localizado em /src/Pct. Tela_Venda_Artigo. Importaremos a classe Widget para criarmos nossas variveis. Import android.widget; O cdigo comentado ficou da seguinte maneira: public class Tela_Venda_ArtigoActivity extends Activity { //Variveis que sero teis no exemplo RadioGroup rgCursos; RadioButton rbLiteDelphi, rbLiteC, rbProfessional, rbStudent; TextView txtCliente, txtCidade, txtEstado; Spinner spEstado; Button btEfetuar; Figura 04: Criando uma classe Registro.
18
fevereiro 2012
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //Fazendo o denominado casting dos componentes utilizados rgCursos = (RadioGroup) findViewById(R.id.rgCursos); rbLiteDelphi = (RadioButton) findViewById(R.id.rbLiteDelphi); rbLiteC = (RadioButton) findViewById(R.id.rbLiteC); rbProfessional = (RadioButton) findViewById(R.id.rbProfessional); rbStudent = (RadioButton) findViewById(R.id.rbStudent); txtCliente = (TextView) findViewById(R.id.edtCliente); txtCidade = (TextView) findViewById(R.id.edtCidade); spEstado = (Spinner) findViewById(R.id.spnEstado); btEfetuar = (Button) findViewById(R.id.btnEfetuar); //Acionar o evento setOnClickListener para o boto Efetuar btEfetuar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { //Instanciar a classe Registro para atribuir os dados digitados pelo usurio Registro reg = new Registro(); reg.cliente = txtCliente.getText().toString(); reg.cidade = txtCidade. getText().toString(); reg.estado = spEstado. getSelectedItem().toString(); //Utilizar o Id do RadioGroup para identificar o tipo de curso switch (rgCursos. getCheckedRadioButtonId()) { case R.id. rbLiteDelphi : reg.curso = 1; break; case R.id.rbLiteC : reg.curso = 2; break; case R.id. rbProfessional : reg.curso = 3; break; case R.id. rbStudent : reg.curso = 4; break; } //Utilizao da Classe Builder para emitirmos um Alerta ao usurio MensagemAlerta(Resultado, Venda Efetuada com sucesso! + \n + ------------------------------------- + Cliente: + reg. cliente + \n+ Cidade: + reg. cidade + / Estado: + reg. estado + \n + Cd. Curso: + reg.curso); } }); } //Implementao de uma funo genrica para utilizar no nosso projeto public void MensagemAlerta(String titulo, String corpo) { AlertDialog.Builder infoResultado = new AlertDialog.Builder(Tela_Venda_ ArtigoActivity.this); infoResultado.setTitle(titulo); infoResultado.setMessage(corpo); infoResultado.setNeutralButton(Ok,null); infoResultado.show(); }
fevereiro 2012
19
Concluso
Este artigo nos demonstrou vrias ideias e conceitos em relao ao Sistema Android. Como sabemos que uma mistura de XML com o Java, primeiramente exploramos os denominados resources com exemplos e utilizaes prticas do mesmo. Procurei trabalhar com componentes diversificados a fim de proporcionar um melhor aprendizado. Trabalhei em cima do evento setOnClickListener para finalmente explorar um pouco da classe Builder que muito utilizada. Como se trata de um exemplo prtico foi criado uma classe para armazenar os registros e futuramente consult-los ou at mesmo gravar em uma base de dados. Espero que vocs estejam gostando desta srie de Artigos de Android, vou ficando por aqui, um forte abrao e at o ms que vem!
Sobre o autor
Thiago Cavalheiro Montebugnoli
Thiago Cavalheiro Montebugnoli tecnlogo, formado pela Faculdade de Tecnologia de Botucatu SP (FATEC) foi consultor tcnico do The Club, j desenvolveu softwares utilizando a plataforma .NET, Delphi junto com Banco de Dados SQL Server e Firebird. Atualmente trabalha no Centro de Processamento de Dados da Prefeitura Municipal de Ita-SP. Possui as seguintes certificaes: MCP - Microsoft Certified Professional, MCTS - Microsoft Certified Technology Specialist, MCAD - Microsoft Certified Application Developer e MCSD - Microsoft Certified Solution Developer.
thiago@theclub.com.br
20
fevereiro 2012
fevereiro 2012
21
Para quem tem pensado em comear a utilizar Lazarus, nesse artigo estarei mostrando mais uma faceta da ferramenta, a importantssima conexo a banco de dados. Lazarus hoje possui conexes nativas para os principais bancos gratuitos existentes: PostgreeSql, MySql, Sqlite e Firebird, alm de possuir conexes para Oracle e ODBC, com a conexo ODBC disponvel, podemos, em ambiente Windows utilizar o Lazarus com Sql Server, um dos bancos de dados mais robustos do mercado. As opes como vimos so variadas, e nesse artigo estarei mostrando a conexo do Lazarus com o banco de dados Open Source mais utilizado pela comunidade Delphi: Firebird. Mos a obra ento!
Com o Lazarus devidamente instalado, abra-o. A primeira caracterstica que verificamos que, comparado com o Delphi, Lazarus possui bem menos abas de componentes. Para nossa tarefa, a aba mais importante a SqlDB. nela que se encontram os componentes que realizam a conexo com as diversas bases de dados que so suportadas pelo Lazarus. A aba SqlDB pode ser vista na figura 1: Os oito ltimos componentes dessa aba so os conectores, que realizam a conexo Lazarus Bancos de dados. Pela ordem temos as seguintes opes de conexo: PostgreeSQL; Oracle; ODBC; Os trs seguintes so para diversas verses do MySQL;
SQLite; Interbase/Firebird
Figura 1
retirando e passando informaes para a base de dados. No artigo, estaremos utilizando o ltimo componente, para Interbase/Firebird. Tambm estaremos utilizando dessa aba os componentes de consulta, no caso o SqlQuery.
Abra o cdigo da unit e na clusula uses coloque a unit: IBConnection. Na seo implementation declare a funo conforme mostrado abaixo:
function ConexaoFirebird(D ataBaseHost,DataBaseName,U serName,Password:String):T IBConnection; var ConnAux:TIBConnection; begin ConnAux:=TIBConnection. Create(nil); ConnAux. HostName:=DataBaseHost; ConnAux. DatabaseName:=DataBaseName; ConnAux. UserName:=UserName; ConnAux.
Password:=Password; ConnAux. Connected:=True; then
Essa funo receber pela ordem, o servidor, ou ip do servidor de banco de dados (em geral localhost), o caminho e nome do arquivo de banco de dados, o nome de usurio e a senha e nos devolver, caso a conexo se realize, um objeto IBConnection com a conexo. Vamos testar a conexo. No evento OnCreate do formulrio principal da aplicao que criamos, iremos chamar essa funo e mostrar o resultado no Caption da janela. Veja o cdigo do evento OnCreate do formulrio:
Conectando ao Firebird
Conforme j mencionado, estaremos utilizando o componente de conexo ao Interbase/Firebird para construirmos um exemplo de manipulao de uma base de dados Firebird. O primeiro passo criarmos nossa base de dados de exemplo. Chamarei a mesma de TheClub_Lazarus_Teste. Veja o script de criao da base:
procedure TForm1. FormCreate(Sender: TObject); var Conn:TIBConnection; begin Conn:=ConexaoFirebird( localhost,C:\TheClub_ Lazarus_ Teste,SYSDBA ,masterkey); if(Conn<>nil)then Caption:=Conexo OK
else Caption:=No foi possvel realizar a conexo; end;
CREATE DATABASE C:\THE_ CLUB_LAZARUS_TESTE.fdb page_size 4096 user SYSDBA password masterkey CREATE TABLE THE_CLUB_ NOMES ( ID_NOME INTEGER NOT NULL, NOME VARCHAR(50), RG VARCHAR(20), CPF VARCHAR(20) );
end;
Aps criarmos a base de dados, faremos o projeto no Lazarus. Abra o Lazarus e inicie um novo projeto do tipo Application.
Por ora no estaremos montando o layout da aplicao. Estaremos criando a funo de conexo ao banco de dados Firebird.
Figura 2
fevereiro 2012
23
QuotedStr(edtCPF.Text)]); try try // Iniciamos a transao e executamos o insert via conexo Trans. StartTransaction; Conn. ExecuteDirect(Sql); Trans.Commit;
Figura 3
ShowMessage(Registro inserido com sucesso); Create(nil); // Criamos a transao para a conexo if(Conn<>nil)then // Se a conexo estiver ok, fazemos a insero dos dados begin Conn. Transaction:=Trans; // Ligamos o componente de transao a conexo // Montamos a frase Sql de insero Sql:=Format(INSERT INTO THE_CLUB_NOMES except // Se ocorrer erro cancela a transao Trans.Rollback; end; finally Trans.Free; Conn.Free; end; end; end;
Repare que no topo da pgina ir aparecer a mensagem avisando que a conexo est funcionando. Essa funo que criamos bastante til porque cria a instncia da conexo ao ser chamada. Isso possibilita utilizarmos uma conexo por acesso ao banco, reduzindo o trfego em uma nica conexo. Na sequncia, faremos a funo de insero de dados em nossa tabela. Utilizaremos essa funo de conexo e atravs dela passaremos os valores via Sql direto. Para inserirmos dados, agora precisamos ter um layout de tela com alguns objetos. Iremos aproveitar o formulrio j existente e montaremos o layout conforme a figura 3. Com o layout criado, faremos a rotina de insero dos dados diretamente no boto Inserir. Criaremos, alm da conexo, um objeto do tipo SqlQuery para inserir os dados. Veja o cdigo do OnClick do boto Inserir:
procedure TForm1. Button1Click(Sender: TObject); var
Sql:string; Conn:TIBConnection; Trans:TSQLTransaction; begin // Conectamos ao banco Conn:=ConexaoFirebird( localhost,C:\TheClub_ Lazarus_Teste, SYSDBA,masterkey); Trans:= TSQLTransaction.
Veja o aplicativo em execuo na figura 4: Para finalizar, vamos completar nosso exemplo com a consulta aos dados. Utilizaremos a mesma tela que estamos utilizando para inserir os dados para essa consulta. S que utilizaremos trs componentes fixos: SqlQuery: Aba sqlDB; DataSource: Aba DataAccess; DbGrid: Aba DataControls;
Figura 4
24
fevereiro 2012
Create(nil); // Criamos a transao para a conexo Conn.Transaction:=Trans; SQLQuery1. DataBase:=Conn; SQLQuery1.Open;// Abrimos a query finally Conn.Free; Trans.Free; end; end;
Figura 5
Veja a consulta em execuo na figura 6: Com a parte de consulta fechamos nosso exemplo de manipulao do banco de dados Firebird com Lazarus.
Figura 6
Concluso
TObject); var Conn:TIBConnection; Trans:TSQLTransaction; begin try SQLQuery1. Close; // Conectamos ao banco Conn:=ConexaoFi rebird(localhost,C:\ TheClub_Lazarus_Teste, SYSDBA,masterkey); Trans:=TSQLTransaction.
Colocaremos tambm um button com o texto Atualizar Consulta para mostrarmos os dados atualizados aps as inseres. Veja como ficar o layout na figura 5 Ligue o DataSource ao SqlQuery atravs da propriedade DataSet e o Dbgrid no DataSource atravs da propriedade DataSource. Na propriedade Sql do SqlQuery coloque a frase:
Nesse segundo artigo sobre Lazarus, demonstrei como utilizar a ferramenta no trato com um dos bancos de dados mais utilizados pela comunidade Delphi atualmente: O Firebird. Firebird um banco que a cada dia vem se tornando mais robusto e confivel. Ento toda ferramenta de automao comercial precisa possuir uma boa integrao com esse banco, e nessa parte Lazarus se mostra totalmente efetivo. Alm do Firebird, estarei mostrando nos prximos artigos como podemos conectar o Lazarus a outros bancos de dados como SqlLite e MySql, excelentes opes de bancos Open Source. isso, espero que tenham gostado e at a prxima!
Na funo de atualizar a consulta, iremos criar a instncia da conexo e ligar a Query a essa instncia para gerarmos a consulta ao abrirmos a Query. Vamos agora ao cdigo do boto Atualizar Consulta:
procedure TForm1. Button2Click(Sender:
Sobre o autor
Antonio Spitaleri Neto
Consultor Tcnico The Club.
antonio@theclub.com.br
fevereiro 2012
25
MTODOS RAVEReport
de A a Z
W
WRITE NULL DATA este mtodo escreve um valor nulo dentro do evento OnGetRow de um componente de conexo de dados. Os dados para os campos personalizados devem ser escritos na mesma ordem em que os campos foram definidos no evento OnGetCols. Example (Delphi)
Connection.WriteNullData( );
branco se no for necessria uma sada pr-formatada. Parmetros NativeData devem conter o contedo no modificvel do campo. Example (Delphi)
Connection.WriteStrData( ,CustomerName );
pontos (dots) para unidades (units) (definida por Unidades em UnitsFactor) Example (Delphi)
XPos := RvNDRWriter1.XD2U( LastXDots );
X
XD2I este mtodo converte a unidade de medida horizontal da tela de impressora de pontos (dots) para polegadas (inches). Example (Delphi)
// Com Units atualmente configuradas para unInch XPos := RvNDRWriter1.XD2I( LastXDots );
XI2D este mtodo converte a unidade de medida horizontal da tela da impressora de inch (polegadas) para pontos (dots). Example (Delphi)
// Com Unidades atuais configuradas para unInch CurrXDots := RvNDRWriter1. XI2D( RvNDRWriter1.XPos );
WRITE STRING DATA Este mtodo grava o contedo de um campo String personalizado (de tipo dtString) dentro do evento OnGetRow de um componente de conexo de dados. Os dados para campos personalizados devem ser escritos na mesma ordem em que os campos foram definidos no evento OnGetCols. Os parmetros formatdata definem o valor formatado do campo, mas podem ficar em
26
fevereiro 2012
XI2U este mtodo converte a medida horizontal de inch (polegadas) para unit (definida pela Units e UnitsFactor). Example (Delphi)
YI2D este mtodo converte a unidade de medida vertical da tela da impressora de polegadas para pontos. Example (Delphi)
// com Units configurada para unInch CurrYDots := RvNDRWriter1. YI2D( YPos );
XU2D este mtodo converte a medida horizontal de units (definida pelas Units e UnitsFactor) para pontos (dots). Example (Delphi)
CurrXDots := RvNDRWriter1. XU2D( RvNDRWriter1.XPos );
ZOOM IN este mtodo adiciona ZoomInc ao ZoomFactor atual e aumenta a imagem na tela. Se um evento OnZoomChange for definido, ento ser chamado e ser responsvel por redesenhar a pgina, caso contrrio, a tela redesenhada. Example (Delphi)
// este cdigo faz com que o ZoomFactor seja incrementado por ZoomInc. RvRenderPreview1.ZoomIn;
XU2C este mtodo converte a medida horizontal de units (definida pelas Units e UnitsFactor) para polegadas (inches). Example (Delphi)
//com units configuradas para unCM CurrXInch := RvNDRWriter1. XU2I( RvNDRWriter1.XPos );
YI2U este mtodo converte a unidade de medida vertical da tela da impressora para unidades de medidas (definida pela Units e UnitsFactor). Example (Delphi)
RvNDRWriter1.YPos := RvNDRWriter1. YI2U( LastYInch );
Y
YD2I este mtodo converte a unidade de medida vertical da tela da impressora de pontos (dots) para polegadas (inches). Example (Delphi)
// com unidades configuradas para unInch YPos := RvNDRWriter1.YD2I( LastYDots );
YU2D este mtodo converte a unidade de medida vertical da tela da impressora DE unidades de medidas (definida pela Units e UnitsFactor) para pontos (dots). Example (Delphi)
CurrYDots := RvNDRWriter1. YU2D( RvNDRWriter1.YPos );
Z
ZOOM OUT - este mtodo subtrai ZoomInc do ZoomFactor atual e diminui a imagem na tela. Se um evento OnZoomChange for definido, ento ser chamado e ser responsvel por redesenhar a pgina, caso contrrio, a tela redesenhada. Example (Delphi)
RvRenderPreview1.ZoomOut;
YU2I este mtodo converte a unidade de medida vertical da tela da impressora de unidades de medidas (definida pela Units e UnitsFactor) para polegadas (inches). Example (Delphi)
YD2U este mtodo converte a unidade de medida vertical da tela da impressora de pontos (dots) para unidades de medidas (definidas pela Units e UnitsFactor). Example (Delphi)
RvNDRWriter1.YPos = RvNDRWriter1.YD2U( LastYDots );
Sobre o autor
Leonora Golin
Consultora Tcnica The Club.
antonio@theclub.com.br
fevereiro 2012
27
Dicas DELPHI
Alterando as configuraes de Rede
Nessa dica ser criado um exemplo de como alterar as propriedades da rede fazendo com que os dados sejam recriados, como o IP, a mscara de rede e o gateway, diretamente pela aplicao.
unit AlteraIP; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) private { Private declarations } public { Public declarations }
Iremos agora copiar o texto da varivel Aplicativo para armazen-la na varivel Arquivo, depois, selecionar o diretrio padro d mquina correspondente, e novamente copiar, s que desta vez copiando o contedo da varivel Diretrio para a varivel WorkDir
begin StrPCopy(Aplicativo, Arquivo); GetDir(0, WorkDir); StrPCopy(Diretorio, WorkDir);
Logo fazemos a verificao de dados inicializados, atravs da procedure FillChar, que ir identificar a quantidade de bytes utilizados no processo, iremos trazer as informaes de inicializao e a condio da sua execuo
FillChar(Inicializacao, Sizeof(Inicializacao), #0); Inicializacao.cb := Sizeof(StartupInfo); Inicializacao.dwFlags := STARTF_ USESHOWWINDOW; Inicializacao.wShowWindow := Visibilidade;
Criamos duas funes, uma que ir receber os dados da conexo, retornando se uma conexo existente ou no, e outra que aguarda a execuo do momento necessrio.
function MudarIp(Conexao, Ip, Mascara, Gateway: string):boolean; function ExecutarAguardando(Arquivo: string; Visibilidade: integer):integer; end; var Form1: TForm1; implementation {$R *.dfm} { TForm1 } function TForm1. ExecutarAguardando(Arquivo: string; Visibilidade: integer): integer;
Logo em seguida, caso o processo no seja criado, retornamos o resultado com o valor de -1, para considerar inutilizado o processo, mas caso j seja considerado um processo criado, ele ir aguardar o processo, retornar o cdigo de sada do processo e exibir o resultado.
if not CreateProcess(nil, aplicativo, nil, nil, false, CREATE_NEW_CONSOLE or NORMAL_ PRIORITY_CLASS, nil, nil, Inicializacao, Processo) then Result := -1 else begin WaitforSingleObject(Processo. hProcess, INFINITE); GetExitCodeProcess(Processo. hProcess,Resultado); Result := Resultado; end;
E por ultimo, a principal alterao, que recupera os dados de configurao de rede e altera de acordo com os dados informados.
begin Result:=(ExecutarAguardando( Configurar o endereco IP netsh +Conexao+ + estatico +Ip+ +Mascara + +GateWay+ 1, SW_ HIDE)=0); end;
O primeiro passo criarmos 6 variveis que serviro para identificar o caminho do diretrio, as informaes de inicializao e de execuo e o resultado geral de hardware
var Aplicativo, Diretorio, WorkDir: string; Inicializacao: TStartupInfo; Processo: TProcessInformation; Resultado: DWord; 28
fevereiro 2012
E para que tenhamos certeza de que as alteraes dos dados da conexo tenham sido realmente alteradas precisamos verificar utilizando a funo MudarIp, se os dados tiveram validade
begin if not MudarIp(Local Area Connection, 192.168.10.22, 255.255.255.0, 192.168.10.90) then ShowMessage(Erro na alterao do IP) else ShowMessage(IP alterado sem erros); end; end; end.
Depois de criada a procedure, utilizada a constante PROCESS_TERMINATE que indexa o caminho do processo declarado, fazendo com que se encerre, alm das variveis que serviro para verificar o formulrio ativo do sistema
procedure TForm1. FinalizaPorCaption(const frmClasse, frmJanela: string); const PROCESS_TERMINATE = $0001; var HWNDJanela: HWND; HandleProc: THandle; IDProcesso: Integer; Classe, Janela: PChar;
Essa dica til pra quem precisa administrar uma rede e necessita de alteraes dos valores dos dados estticos de suas conexes, atravs da prpria aplicao facilitar e exigir menos contato com interno as configuraes do sistema operacional.
A procedure verifica se a classe e a janela esto vazias e ento declarando seus valores como nil, caso no, ela resgata os valores passados pelos parmetros da procedure, fornecidas pelo usurio.
begin try if Classe = then Classe:= nil else Classe:= PChar(Classe); if Janela = then Janela:= nil else Janela:= PChar(Janela);
Essa dica utiliza um procedimento que ir controlar a restrio de acesso determinados formulrios fechando a aplicao, de acordo com o Caption do formulrio ao tentar acess-la
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) private { Private declarations }
Aps identificado o formulrio da aplicao iremos armazen-la na varivel do tipo HWND que controla os processo em execuo e o GetWindowThreadProcessID, identificando a janela e o ndice do processo
HWNDJanela:= FindWindow(Classe, Janela); GetWindowThreadProcessID(HWNDJa nela, @IDProcesso);
importante declar-la como sendo pblica para que todas as outras units possam enxerga-la.
public { Public declarations } procedure FinalizaPorCaption(const frmClasse, frmJanela: string); end; var Form1: TForm1; implementation {$R *.dfm} { TForm1 }
finally HandleProc:= OpenProcess(PROCESS_TERMINATE, False, IDProcesso); TerminateProcess(HandleProc, 4); end; end; end.
Concluso
Esse procedimento serve como, por exemplo, para as permisses de usurio, ou encerrar foradamente um acesso no permitido, ocasionando o encerramento do sistema.
fevereiro 2012
29
Horizontal
Vertical
30
fevereiro 2012
fevereiro 2012
fevereiro 2012