![]() |
||||||||
|
|
||||||||


| com exemplos em VB |
| Componente para deixar forms em Vb semelhantes às telas do winnamp |
| Componente para colocar sua aplicação VB no Systray |
| Componente para transformar sua aplicação VB em serviço |
| Ferramentas úteis para quem usa Olap Server |
| |

|
||||
Pesquisa personalizada
Gerando mapas de site para o ASP.NET O ASP.NET possui um novo e poderoso recurso, que é a geração de mapas de sites. Vocês podem ler mais sobre esse recurso em http://www.bufaloinfo.com.br/artigos/coluna28.asp Porém, como vocês lerão, não existe nenhum recurso, nenhum wizard, que simplifique a montagem do XML para ser utilizado como mapa de site. Não gosto de deixar nada incompleto. Escrevi sobre a criação de mapas de sites, mas ter que escrever o XML manualmente, na era em que estamos de desenvolvimento web, é impensável. Então vou nesse artigo demonstrar a criação de uma aplicação windows com o objetivo de gerar automaticamente o mapa do site, a partir de simples configuração. Precisaremos de uma Treeview para exibir o novo mapa do site de forma hierárquica e também precisaremos de um pequeno cadastro para adicionar novos nós. Na versão anterior, utilizariamos um splitter para dividir a tela em 2. Na nova versão temos um objeto chamado splitcontainer que substitui o splitter, já trazendo um panel em cada um dos seus lados. É verdade que o splitcontainer simplifica o trabalho de uso de um splitter, trazendo as configurações mais simples em termos de panels já prontas. Porém fico em dúvida se um splitcontainer será tão flexivel como o antigo splitter. Não consegui, por exemplo, fazer com que o foco iniciasse em uma textbox do 2o panel. Mas lembrem-se : Estamos trabalhando com uma versão beta, muita coisa vai melhorar até a versão final. Em uma das metades do splitcontainer vamos inserir uma treeview, na outra, um pequeno cadastro no qual precisaremos dos campos "Titulo", "URL" e "Descrição" e dois botões, "Gravar" e "Alterar".
Precisaremos tabém de um menu na aplicação. Veja como ele ficará :
Não vou inserir as informações do cadastro diretamente no treeview. Se assim fizesse teria depois o trabalho de gerar o XML "manualmente". Vamos sim gerar um dataSet com a estrutura do mapa do site para que possamos inserir nele os dados cadastrados e obter como resultado o XML de mapa do site automaticamente. Veja o XML de mapa do site que deveremos gerar : <siteMap> Como vocês podem observar, a estrutura é hierárquica. Então como colocar isso em um dataSet ? A dúvida surge devido a visão normalmente relacional do dataSet. Porém, por mais que normalmente nos limitemos a fazer o dataSet espelhar dados relacionais, ele é em seus fundamentos um objeto para manipulação de XML e, consequentemente, hierárquico. Já fizemos isso antes : No artigo em .... utilizamos uma estrutura semelhante para fazer a geração de documentos RSS a partir de um DataSet. Comparando com a visão relacional, isso é na verdade um dataSet chamado siteMap que contém uma tabela chamada siteMapNode e esta contém um auto-relacionamento, ligando registros da tabela siteMapNode com outros também da siteMapNode. Mas é possível fazer um relacionamento sem chaves (reparem que a diferença entre a estrutura hierárquica do XML e a estrutura relacional e a ausência de chaves de ligação, não são necessárias) ? Sim, é possível, mas como vocês verão não estaremos totalmente "sem chaves". Então vamos por mãos a obra. Precisamos adicionar um novo dataSet em nossa aplicação. Porém temos um problema : O novo editor de dataSets no Visual Studio 2005 é diferente do editor de XML, não possui mais a flexibilidade de edição de um schema. Desta forma se utilizarmos Add New Item e pedirmos para adicionar um dataSet não conseguiremos chegar ao resultado desejado. Precisamos então utilizar o add new item mas pedir para adicionar um XML Schema e não um dataSet. Adicionando um XML Schema teremos a liberdade de editar o schema mais a vontade, apesar de que mesmo com esta opção não temos mais como visualizar o XML, como faziamos livremente no Visual Studio 2003. Vamos utilizar a toolbox para adicionar elementos em nosso schema. Ao invés de criar um Element, representando uma tabela, faremos um ComplexType. Um ComplexType é como definirmos um "tipo de tabela", sem definirmos a tabela em si, apenas o tipo. Vamos chamar o complexType de siteMapNode, precisa ser assim, identico em maiúsculas e minúsculas, para que o XML seja gerado desta forma e assim compreendido pelo ASP.NET Vamos inserir os seguintes campos : title, description e url. Todos os 3 como string. Observe que ao lado dos campos aparece a letra "E". Isso significa que no XML cada campo será um elemento contendo seu valor. Porém não é isso que precisamos : No XML usado pelo ASP.NET esses 3 campos são atributos, não elementos. Então clique na letra "E", surgira uma combo que permitirá você troca-la. Selecione a opção Attribute, a letra "E" será trocada pela letra "A", indicando que os campos passarão a ser guardados como atributos, porém isso não afetará o dataset.
Vamos então adicionar um campo a mais. Esse campo a mais se chamará siteMapNode e o tipo deste campo a mais será siteMapNode, isso mesmo, será do tipo do nosso complexType. Desta forma estamos informando que dentro de nosso complexType podemos ter diversas estruturas repetitivas de siteMapNode, infinitamente. Feita essa definição, agora sim vamos inserir um Element. No tipo do Element (espaço ao lado do nome), indique o tipo siteMapNode. Agora sim, criamos um elemento do tipo de nosso complexType, concluindo a tarefa de criação do DataSet.
Por fim, vamos abrir a janela de propriedades e definir a propriedade targetNamespace com o valor http://schemas.microsoft.com/AspNet/SiteMap-File-1.0 . Este é o nome dado a linguagem de siteMaps criada pela MS, colocando este nome em nosso targetNamespace ficará bem simples a gravação e recuperação de arquivos de siteMaps. O nome precisa ser identico ao que está ai, inclusive maiusculas e minusculas.
Porém quando inserimos esse arquivo no projeto selecionamos um XML Schema. Assim sendo este arquivo ainda não é um dataSet. Para que este arquivo seja interpretado como um dataSet precisamos que seja processado pela CustomTool do Visual Studio para processamento do dataSet. Assim sendo precisamos selecionar o arquivo no solution explorer, abrir a janela de propriedades e em "Custom Tool" digitar MSDataSetGenerator.
Vamos então inserir um dataSet no form. Simplesmente selecionando o dataSet na toolbox e inserindo no form é mostrada uma tela perguntando qual será o tipo do dataset, sendo que siteMap aparecerá como uma das opções, a que escolheremos. Vamos agora codificar o botão "Adicionar". Fazendo isso muitas dúvidas sobre o uso deste dataSet serão solucionadas. Antes do código propriamente, vamos acertar alguns detalhes : Precisamos adicionar um nó inicial na treeview. Vamos chamar esse nó de site.
O código do botão adicionar precisará fazer as seguintes tarefas :
Mas na descrição acima foram citadas várias vezes as chaves primária e estrangeira, mas nós não temos isso no nosso dataSet! Ai está o segredo : Nós temos as chaves ! As chaves são absolutamente necessárias para que nossa classe siteMap entenda a estrutura dos dados sem que tenhamos que manipular XML diretamente. Sabendo disso, a custom tool que criou essa classe automaticamente para nós também criou as chaves. A chave primária do registro passou a se chamar siteMap_ID enquanto que a chave estrangeira, que indica a que outro registro ele encontra-se ligado, passou a chamar-se siteMap_ID_0 . Existe um padrão através do qual esses nomes podem ser identificados, mas havendo dúvidas você pode pedir para ver os arquivos ocultos, no solution explorer, abrir o arquivo .VB e buscar as chaves, procurando pelo trecho de código em que é criado o objeto relation. Ok, então temos as chaves. Vamos então preenche-las. como ? Vamos manter um contador, uma variável a nível de classe, para gerar a chave primária. Já a chave estrangeira vai ser preenchida com o tag do treenode que está selecionado (que será o pai do novo elemento). Veja como fica o código : 49 Private Sub TreeView1_AfterSelect(ByVal sender As System.Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles trvMap.AfterSelect 50 51 52 cmdAdicionar.Enabled = True 53 54 End Sub 46 Dim linha As siteMap.siteMapNodeRow 47 48 If trvMap.SelectedNode.Tag = 0 And trvMap.Nodes.Count > 1 Then 49 50 51 MsgBox("Apenas pode haver uma página principal") 52 Exit Sub 53 End If 54 55 indiceNo += 1 56 57 linha = SiteMap1.siteMapNode.NewsiteMapNodeRow 58 59 60 linha.description = txtdescricao.Text 61 linha.url = txtUrl.Text 62 linha.title = txtTitulo.Text 63 linha("sitemapnode_id") = indiceNo 64 65 If trvMap.SelectedNode.Tag <> 0 Then 66 linha("sitemapnode_id_0") = trvMap.SelectedNode.Tag 67 68 69 End If 70 71 CriarNo(trvMap.SelectedNode, linha) 72 73 SiteMap1.siteMapNode.Rows.Add(linha) 74 75 76 limparCaixas() 77 trvMap.ExpandAll() Observe que dois trechos de código foram transformados em rotinas separadas : A criação de um nó na treeView e a limpeza das caixas de texto. Isso é algo intuitivo, gerando essas subs de forma a garantir que essas tarefas sejam centralizadas e não duplicadas durante o código. De fato, a necessidade da sub CriarNo só percebi após ter chegado quase ao fim do código. Se teorizarmos, provavelmente descobriremos que este ato intuitivo esteja ligado a alguma regra de refactoring. Mas procurar tornar este tipo de codificação intuitiva deve ser o objetivo de todo desenvolvedor. Veja como ficam as rotinas : 86 Private Sub limparCaixas() 87 txtTitulo.Text = "" 88 txtdescricao.Text = "" 89 txtUrl.Text = "" 90 91 txtTitulo.Focus() 92 End Sub 93 94 Private Function CriarNo(ByVal noPai As TreeNode, ByVal linha As DataRow) As TreeNode 95 96 97 Dim trno As TreeNode 98 trno = New TreeNode 99 trno.Text = linha("title") 100 trno.Tag = linha("sitemapnode_id") 101 noPai.Nodes.Add(trno) 102 Return (trno) 103 End Function Controle de alterações Vamos adicionar na aplicação um mecanismo de segurança : Se o usuário pedir para sair da aplicação e houverem alterações não salvas no mapa vamos interroga-lo se deseja realmente sair da aplicação. Para isso basta programar o evento closing do form, veja como fica : 105 Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing 106 107 108 If SiteMap1.HasChanges Then 109 Dim resp As MsgBoxResult 110 resp = MsgBox("Existem alterações no mapa, tem certeza de que deseja sair ?", MsgBoxStyle.Exclamation Or MsgBoxStyle.DefaultButton2 Or MsgBoxStyle.YesNo, "Tem certeza ?") 111 112 113 114 If resp = MsgBoxResult.No Then 115 e.Cancel = True 116 End If 117 End If 118 End Sub Drag-And-Drop A propria estrutura das informações já nos sugere a realização de Drag-And-Drop. O usuário certamente vai desejar arrastar nós para outra posição, corrigindo a hierarquia do site. Então vamos fazer a programação de eventos de drag-and-drop da treeview. Devemos começar com a programação do evento itemDrag, no qual aprovaremos a realização do drag-and-drop. Feito isso programamos o evento dragEnter, aceitando o objeto e o evento dragdrop, realizando a transferência. No dragdrop precisaremos retirar o nó arrastado de dentro da Treeview e inseri-lo em sua nova posição. Precisaremos também localizar esse registro no dataSet e alterar a chave estrangeira, indicando qual é o novo nó pai do que foi arrastado. Observe no código que não permitiremos que um nós seja arrastado para "Site", gerando a duplicação de páginas iniciais do site. 121 Private Sub trvMap_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles trvMap.DragEnter 122 123 124 125 e.Effect = e.AllowedEffect 126 End Sub 127 128 Private Sub trvMap_ItemDrag(ByVal sender As Object, ByVal e As System.Windows.Forms.ItemDragEventArgs) Handles trvMap.ItemDrag 129 130 131 132 If e.Button = Windows.Forms.MouseButtons.Right Then 133 134 135 DoDragDrop(e.Item, DragDropEffects.Move) 136 End If 137 End Sub 138 139 Private Sub trvMap_DragDrop(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles trvMap.DragDrop 140 141 142 143 Dim p As New Point(e.X, e.Y) 144 Dim tndrop As TreeNode 145 Dim tn As TreeNode 146 147 p = trvMap.PointToClient(p) 148 tndrop = trvMap.GetNodeAt(p) 149 150 If Not IsNothing(tndrop) AndAlso tndrop.Tag <> 0 Then 151 152 153 tn = e.Data.GetData(GetType(TreeNode)) 154 trvMap.Nodes.Remove(tn) 155 tndrop.Nodes.Add(tn) 156 157 Dim dr As siteMap.siteMapNodeRow 158 dr = SiteMap1.siteMapNode.Rows.Find(tn.Tag) 159 160 dr.BeginEdit() 161 dr("sitemapnode_id_0") = CType(tndrop.Tag, Integer) 162 163 dr.EndEdit() 164 trvMap.ExpandAll() 165 End If 166 End Sub No evento DragDrop foi necessário fazer uso de alguns recursos gráficos para encontrar o nó destino, para o qual está sendo feito o drop. Obtivemos o ponto de tela no qual o objeto foi soltado, utilizamos o método PointToClient para converte-lo para um ponto na TreeView e finalmente o GetNodeAt para obtermos o objeto TreeNode. Para a edição da chave estrangeira um simples Find nos levou até a linha que deveria ser editada. Observe que o nó de origem é transmitido do evento ItemDrag para o evento DragDrop através da chamada do DoDragDrop. O treenode chega na propriedade Data do objeto "e" e é recuperado pelo método GetData.
Alteração Para deixar a aplicação flexivel precisaremos também permitir a alteração de dados dos nós. Para que o usuário possa alterar um nó, vamos utilizar o duplo clique. Assim o usuário faz duplo clique em um nó para entrar em modo de alteração deste nó. Quando isso acontecer precisaremos desabilitar o botão adicionar, habilitar o botão o botão Alterar e preencher as caixas de texto. 169 Private Sub trvMap_NodeMouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeNodeMouseClickEventArgs) Handles trvMap.NodeMouseDoubleClick 170 171 172 173 Dim tn As TreeNode 174 175 tn = e.Node 176 If Not IsNothing(tn) AndAlso tn.Tag <> 0 Then 177 Dim dr As siteMap.siteMapNodeRow 178 179 dr = SiteMap1.siteMapNode.Rows.Find(tn.Tag) 180 txtdescricao.Text = dr.description 181 txtUrl.Text = dr.url 182 txtTitulo.Text = dr.title 183 cmdAdicionar.Enabled = False 184 cmdAlterar.Enabled = True 185 tnAlterando = tn 186 End If 187 End Sub 188 189 190 Private Sub cmdAlterar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdAlterar.Click 191 192 193 Dim dr As siteMap.siteMapNodeRow 194 195 dr = SiteMap1.siteMapNode.Rows.Find(tnAlterando.Tag) 196 197 198 dr.BeginEdit() 199 dr.title = txtTitulo.Text 200 dr.url = txtUrl.Text 201 dr.description = txtdescricao.Text 202 dr.EndEdit() 203 204 limparCaixas() 205 cmdAlterar.Enabled = False 206 cmdAdicionar.Enabled = True 207 End Sub Salvar Vamos agora criar a possibilidade de salvar o conteúdo do dataSet em um arquivo XML para ser utilizado no ASP.NET Precisaremos inserir um objeto SaveFileDialog para poder interrogar o usuário sobre com que nome deseja salvar o arquivo. Estes objetos dialog nos permitem fazer uso de janelas de dialogo pré-definidas pelo sistema operacional, mantendo nosso sistema integrado com o SO. Devemos configurar a propriedade Filter para indicar os tipos de arquivo que estaremos podendo salvar, após isso o objeto está pronto para ser usado. Veja como fica o Filter :
Desta forma a caixa de dialogo para salvar permitirá salvarmos o arquivo com a extensão XML ou já com a extensão sitemap, que é a extensão esperada pelo ASP.NET No código, após perguntar o nome do arquivo usaremos writeXML do dataSet para gravar o dataSet em disco como XML. O fato de termos configurado o targetNamespace do dataSet exatamente com o nameSpace dos siteMaps deixa essa tarefa bem simples. Apenas precisaremos garantir que o schema não seja gravado junto. Outra questão interessante são os menus, Salvar e Salvar Como. O menu Salvar deverá ser habilitado apenas quando houverem alterações a serem salvas. Já o menu Salvar Como deverá estar habilitado sempre. O menu Salvar apenas perguntará o nome do arquivo se este já não tiver um nome. Se o arquivo houver sido aberto e desta forma já possuir um nome, o menu Salvar não pergunta o nome, apenas salva o arquivo. veja como fica o código : 210 Private Sub mnuSalvarComo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuSalvarComo.Click 211 212 213 SalvarArquivo() 214 End Sub 215 216 Private Sub mnuSalvar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuSalvar.Click 217 218 219 GravarArquivo() 220 End Sub 221 222 Private Sub SalvarArquivo() 223 If SD.ShowDialog() = Windows.Forms.DialogResult.OK Then 224 nomeArquivo = SD.FileName 225 GravarArquivo() 226 MsgBox("Arquivo Gravado!", MsgBoxStyle.Information, "Gravado!") 227 228 229 End If 230 End Sub 231 232 233 Private Sub GravarArquivo() 234 If nomeArquivo = "" Then 235 SalvarArquivo() 236 Else 237 SiteMap1.WriteXml(nomearquivo, XmlWriteMode.IgnoreSchema) 238 239 240 SiteMap1.AcceptChanges() 241 End If 242 End Sub Observe o uso do AcceptChanges : com isso fazemos um "reset" nos status dos registros do dataset, para que a mensagem do evento closing não seja provocada caso o usuário feche a aplicação, afinal, neste momento não existem informações alteradas no mapa do site. Abrir Para abrir o arquivo precisaremos utilizar o OpenFileDialog, ao invés do SaveFileDialog. Da mesma forma, configuraremos a propriedade Filter exatamente como fizermos no caso do SaveFileDialog. Bastará utilizarmos o ReadXML do objeto dataSet para lermos o arquivo XML. Mas após a leitura precisaremos atualizar o valor do contador da chave primária, para garantir que a edição do arquivo seja feita corretamente. Feito isso teremos um problema adicional : Preencher a treeview com as informações que acabamos de ler. Como se trata de uma árvore hierárquica precisaremos utilizar recursividade para realizar o preenchimento, o código fica bem interessante. Veja como fica o código : 245 Private Sub mnuAbrir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuAbrir.Click 246 247 248 abrirArquivo() 249 preencherTree() 250 End Sub 251 252 Private Sub abrirArquivo() 253 If OD.ShowDialog = Windows.Forms.DialogResult.OK Then 254 255 256 SiteMap1.ReadXml(OD.FileName) 257 SiteMap1.AcceptChanges() 258 nomeArquivo = OD.filename 259 indiceNo = SiteMap1.siteMapNode.Compute("max(sitemapnode_id)", "") 260 261 262 End If 263 End Sub Observe que o evento click do menu abrir além de chamar a sub para abertura do arquivo também chama uma sub para o preenchimento da TreeView. A sub preencherTree faz o primeiro passo no preenchimento, veja : 265 Private Sub preencherTree() 266 Dim trno As New TreeNode 267 trvMap.Nodes.Clear() 268 trno.Text = "Site" 269 trno.Tag = 0 270 trvMap.Nodes.Add(trno) 271 SiteMap1.siteMapNode.DefaultView.RowFilter = "sitemapnode_id_0 is null" 272 273 274 ConverterLinhasNos(trno, SiteMap1.siteMapNode.DefaultView) 275 276 trvMap.ExpandAll() 277 End Sub Todo o conteúdo da Treeview é eliminado, o nó inicial, "Site", é recriado e é chamado o método ConverterLinhasNos, tendo como parâmetro o nó inicial (que será pai de todos) e uma dataView. Este método é recursivo, como veremos a seguir. Por fim, quando o processamento terminar, toda a treeview já estará preenchida, então simplesmente fazemos um ExpandAll para exibir todos os nós. Observe que foi colocado um RowFilter na DataView antes de passa-la para o método ConverterLinhasNos. Estamos fazendo a primeira chamada, o objetivo é preencher os filhos do nó "Site" e estes tem o campo sitemapnode_id_0 como null, dai o RowFilter para inicialmente localizar apenas estas linhas. Veja como fica o código do ConverterLinhasNos : 279 Private Sub ConverterLinhasNos(ByVal noPai As TreeNode, ByVal dv As Data.DataView) 280 281 282 Dim dr As DataRowView 283 For Each dr In dv 284 Dim linhasFilho As DataView 285 Dim trno As TreeNode 286 trno = CriarNo(noPai, dr.Row) 287 linhasFilho = dr.CreateChildView("siteMapNode_siteMapNode") 288 289 290 If linhasFilho.Count > 0 Then 291 ConverterLinhasNos(trno, linhasFilho) 292 End If 293 Next 294 End Sub É necessário processar cada linha da DataView, inserindo os elementos no treeNode pai. Para a inserção dos elementos reaproveitamos aqui a função CriarNo. Fica então explicado porque foi criada como função e não como sub. Para cada elemento da dataView é necessário descobrir se o elemento possui nós filhos ou não. Para isso utilizamos o método CreateChildView. Este método, existente em objetos do tipo DataRowView - linhas de dataView, recebe como parâmetro o nome de um DataRelation e nos devolve uma nova dataView apenas com os registros filhos desta linha. Então se houverem registros filhos (count>0) precisamos adiciona-los no treeview, como filhos do nó que acabamos de criar. É neste ponto que esta sub se torna recursiva, chamando novamente ela mesma. Observe que o nível de recursividade é indeterminado, será tantos quantos forem os níveis de nós filhos. Conclusão Com minha experiência em .NET 1.1, foi necessário muito pouco estudo específico para poder criar esta aplicação em .NET 2.0 . Isso é uma ótima demonstração de como será fácil a passagem da versão 1.1 para a versão 2.0 Também é importante observar como ficou mais difícil realizar uma tarefa um pouco fora de padrões comuns, como observamos com a criação de nosso dataSet. Assim sendo, quem não tiver experimentado situações assim no .NET 1.1 terá muita dificuldade de entender esse mecanismo e aproveitar-se dele no .NET 2.0. Vemos então a importância do estudo do .NET 1.1 Também é importante destacar que todo o ambiente de desenvolvimento da versão 2.0 está repleto de pequenas melhorias que geram aumento de produtividade no desenvolvimento, mas algumas coisas são tão sutis que chegam a passar despercebidas quando se está envolvido com o desenvolvimento. Por exemplo, ao trocar uma sub por function o end sub vira end function automaticamente. Isso passará despercebido ao desenvolvedor, o que será notado depois será a produtividade final do desenvolvimento.
Dennes Torres |
||||
|
Veja abaixo os comentários já enviados :
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Quer
saber mais?
Faça um curso na Búfalo Informática, Treinamento e Consultoria e Prepare-se para o Mercado! Veja o que a Búfalo tem para você. |
||||
© Búfalo Informática,
Treinamento e Consultoria -
Rua Álvaro Alvim, 37 Sala 920 • Cinelândia • Rio de Janeiro • RJ
Tel.: (21)2262-1368 • e-Mail: contato@bufaloinfo.com.br