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


| 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
Imprimir Baixe os fontes Obtendo Schemas de base de dados utilizando o ADO.NET Existem muitas aplicações que precisam obter informações sobre a estrutura de objetos na base de dados, isso é algo bem frequente. Aplicações para geração de código, por exemplo, precisam de amplas informações sobre a estrutura das bases de dados. O data provider para OLEDB implementa recursos para obtermos a estrutura de bases de dados. Como trata-se de um data provider ligado a inúmeros bancos de dados (através dos providers OLEDB), podemos obter a estrutura de inúmeros tipos de banco de dados diferentes. Toda essa possibilidade encontra-se no método chamado GetOleDbSchemaTable. Este método recebe 2 parâmetros : Um GUID que identifica o tipo de objeto que desejamos (tabelas, procedures, views, etc..) e um 2o parâmetro que atua como filtro para os dados que serão retornados. Este método devolve uma dataTable, tornando simples a manipulação dos dados no client. Vamos então montar um pequeno projeto que nos permita acessar e trabalhar com os dados sobre a estrutura do banco, demonstrando desta forma como manipular estes dados, entre outros exemplos úteis. Vamos inicialmente criar uma Windows Application com um form e colocar dois objetos no form : Uma listBox (lstObjetos) e uma DataGrid (dg). Vamos preencher a listbox com uma lista dos tipos de objetos que poderemos estar requisitando da base de dados. Mas este preenchimento não será tão simples : Os tipos de objetos não estão em um enum, estão disponíveis na forma de propriedades da classe System.Data.OleDb.OleDbSchemaGuid, cada uma devolvendo o GUID do tipo de objeto correspondente. Precisaremos então utilizar Reflections para podermos preencher a listbox, veja : 58 Dim p As New ArrayList 59 Dim t As Type 60 t = GetType(System.Data.OleDb.OleDbSchemaGuid) 61 p.AddRange(t.GetFields) 62 lstObjetos.DataSource = p 63 lstObjetos.DisplayMember = "Name" Desta forma vinculamos a listbox a um arrayList de objetos Field, que devolvem os campos existentes na classe OleDbSchemaGuid.
Sempre que houver um click na listbox vamos obter os dados referentes a este tipo de objeto e vamos exibir tais dados na dataGrid. A lista de objetos, porém, é genérica. Portanto nem todos os objetos estão disponíveis para todos os tipos de banco, precisaremos tratar isso também. Veja como fica : 58 Dim ds As New DataSet 59 Dim nomeTabela As String 60 nomeTabela = DirectCast(lstObjetos.SelectedItem, FieldInfo).Name 61 62 63 Dim dt As DataTable 64 CN.Open() 65 Try 66 dt = CN.GetOleDbSchemaTable(DirectCast(lstObjetos.SelectedItem, FieldInfo).GetValue(GetType(System.Data.OleDb.OleDbSchemaGuid)), Nothing) 67 Catch er As Exception 68 MsgBox("Este tipo de elemento não está disponível neste provider OLEDB" & vbCrLf & er.Message) 69 Exit Sub 70 Finally 71 CN.Close() 72 End Try 73 74 ds.Tables.Add(dt) 75 76 77 dg.DataSource = ds.Tables(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) Observe a expressão, um pouco mais complexa, utilizada como primeiro parâmetro no método GetOledbSchemaTable. Tendo o objeto FieldInfo, precisamos utilizar o método GetValue deste objeto para obter o valor de uma propriedade. Porém como as propriedades em questão são Shared, a aplicação do GetValue recebe como parâmetro um type, a própria classe OleDbSchemaGuid .
Pronto. Com essa codificação simples já temos acesso a informações de estrutura da base de dados. Agora vamos tornar esta aplicação um pouco mais sofisticada. Vamos adicionar a possibilidade do usuário fazer personalizações no que é exibido (por exemplo, cortando tipos de objetos desnecessários na listbox e colunas desnecessárias na grid) e manter a configuração feita pelo usuário entre as execuções da aplicação. Vamos começar com a listbox. Além de possibilitar a deleção de itens da listbox precisaremos guardar os itens que foram deletados em um arraylist, o arrayList dos deletados. Poderemos serializar este arraylist para um arquivo em disco e recupera-lo posteriormente, quando a aplicação for novamente aberta. Então vamos ver como isso fica : 86 Private Sub lstObjetos_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles lstObjetos.KeyDown 87 If e.KeyData = Keys.Delete Then 88 Dim c As CurrencyManager 89 ar.Add(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) 90 remover(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) 91 92 c = Me.BindingContext(p) 93 c.SuspendBinding() 94 c.ResumeBinding() 95 96 End If 97 End Sub Observe que temos então 2 arrayLists : Um arraylist com os elementos exibidos e um arraylist para guardar os elementos deletados. No exemplo acima adicionamos o nome do item deletado no arrayList de deletados (AR) e eliminamos o item deletado do array de itens exibidos. Para isso criei uma sub a parte chamada remover. Quanto ao uso do CurrencyManager, acima, isso se deve ao fato de que uma listbox, quando vinculada a um arraylist, não se atualiza automaticamente quando o arrayList se atualiza. Esse assunto foi tema de uma dica no site BufaloInfo, veja em http://www.bufaloinfo.com.br/dicas.asp?cod=725 Veja como fica a sub Remover : 99 Private Sub remover(ByVal nome As String) 100 For Each f As FieldInfo In p 101 If f.Name = nome Then 102 p.Remove(f) 103 Exit Sub 104 End If 105 Next 106 End Sub Como o arrayList ligado a combo possui objetos FieldInfo foi necessário localizar o objeto FieldInfo correto para poder elimina-lo. Agora vejamos como fica o processo de serialização e deserialização do arrayList. Este código é especialmente interessante pois esta tarefa pode ser realizada com qualquer objeto que tenha capacidade de serialização - e são muitos. Veja o código de serialização : 58 If File.Exists(NomeHash) Then 59 File.Delete(NomeHash) 60 End If 61 Dim obj As New BinaryFormatter 62 Dim st As New FileStream(NomeHash, FileMode.Create) 63 obj.Serialize(st, ar) 64 st.Close() Como trata-se da gravação de um arquivo em disco, tomei o cuidado de verificar se o arquivo existe e, caso exista, deleta-lo. Com a classe BinaryFormatter (que fica em System.Runtime.Serialization.Formatters.Binary ) fica fácil fazer o processo de serialização e deserialização de um objeto, como pode observar acima. O FileStream, localizado em System.IO, completa o trabalho fazendo a gravação do resultado da serialização em um arquivo em disco. Veja como fica a deserialização, tão simples quanto : 59 If File.Exists(NomeHash) Then 60 Dim obj As New BinaryFormatter 61 Dim st As New FileStream(NomeHash, FileMode.Open) 62 ar = obj.Deserialize(st) 63 st.Close() 64 For Each f As String In ar 65 remover(f) 66 Next 67 End If Observe que após a deserialização já implementei o que desejamos : faço uma varredura no arraylist e a eliminação dos elementos deletados do arraylist principal. Neste trecho de código utilizei um truque interessante : Em todos os pontos nos quais me refiro ao nome do arquivo utilizei uma chamada a uma propriedade, NomeHash. O nome do arquivo propriamente é uma montagem que depende do local onde a aplicação estiver. Utilizando uma propriedade, NomeHash, centralizo em um único ponto do código a geração do nome do arquivo. Assim sendo se em algum momento for necessário alterar o código de geração do nome deste arquivo bastará altera-lo em um único local. Veja como fica a propriedade : 98 Private ReadOnly Property NomeHash() As String 99 Get 100 Return (Application.StartupPath & "\hash.bin") 101 End Get 102 End Property Vamos agora permitir que o usuário personalize as colunas na DataTable e mantenha essa personalização através das execuções da aplicação. Para permitir a deleção de colunas na grid vamos criar um contextMenu. Porém para que possamos saber em que coluna o mouse estava no momento em que o contextMenu for ativado não irei vincula-lo diretamente na propriedade contextMenu da grid, mas sim ativa-lo através do evento mouseUp. Veja como fica : 104 Private Sub dg_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles dg.MouseUp 105 If e.Button = MouseButtons.Right Then 106 pt = New Point(e.X, e.Y) 107 ContextMenu1.Show(dg, pt) 108 End If 109 End Sub Observe que guardei na variável pt um objeto point com as coordenadas do click. Desta forma poderemos utilizar este objeto para descobrir qual foi a coluna clicada. Veja como fica o click do menu : 111 Private Sub MenuItem1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MenuItem1.Click 112 113 Dim hit As DataGrid.HitTestInfo 114 hit = dg.HitTest(pt) 115 DirectCast(dg.DataSource, DataTable).Columns.RemoveAt(hit.Column) 116 117 End Sub Através do método HitTest podemos obter informações sobre um determinado ponto com relação a datagrid, descobrindo, por exemplo, qual foi a coluna clicada, para remove-la em seguida como é feito no código acima. Partimos então para o próximo problema : Como manter a escolha das colunas através das execuções da aplicação ? A melhor forma de fazer isso é guardando o schema do DataSet em um arquivo em disco. Precisaremos então fazer pequenas adaptações na forma de carregar o dataSet : No nosso exemplo anterior, simplesmente adicionavamos a dataTable. Agora precisaremos manter um dataSet único e chegar se a dataTable já existe ou não. Se não existe, simplesmente adicionamos, mas se já existe então precisamos preencher os dados dentro da estrutura da dataTable já existente, fazendo um Merge. Veja como fica : 119 Private Sub LerSchema(ByVal nome As System.Guid) 120 Dim dt As DataTable 121 CN.Open() 122 Try 123 dt = CN.GetOleDbSchemaTable(nome, Nothing) 124 Catch er As Exception 125 MsgBox("Este tipo de elemento não está disponível neste provider OLEDB" & vbCrLf & er.Message) 126 Exit Sub 127 Finally 128 CN.Close() 129 End Try 130 If Not ds.Tables.Contains(dt.TableName) Then 131 ds.Tables.Add(dt) 132 Else 133 ds.Merge(dt, False, MissingSchemaAction.Ignore) 134 End If 135 End Sub Observe no código acima que o Merge faz uso do MissingSchemaAction. Isso porque o usuário pode ter eliminado alguns campos da tabela. Se o Merge não definisse essa opção os campos seriam re-adicionados. Outra questão interessante é que neste ponto já transformei essa rotina em uma sub LerSchema. Essa sub é chamada do SelectedIndexChanged da listbox. Veja como fica : 137 Private Sub lstObjetos_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lstObjetos.SelectedIndexChanged 138 139 Dim nomeTabela As String 140 nomeTabela = DirectCast(lstObjetos.SelectedItem, FieldInfo).Name 141 If Not ds.Tables.Contains(nomeTabela) Then 142 143 LerSchema(DirectCast(lstObjetos.SelectedItem, FieldInfo).GetValue(GetType(System.Data.OleDb.OleDbSchemaGuid))) 144 ElseIf ds.Tables(nomeTabela).Rows.Count = 0 Then 145 LerSchema(DirectCast(lstObjetos.SelectedItem, FieldInfo).GetValue(GetType(System.Data.OleDb.OleDbSchemaGuid))) 146 End If 147 148 dg.DataSource = ds.Tables(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) 149 150 End Sub Se a tabela não existe no dataSet ou se está sem linhas é chamada a sub LerSchemas, do contrário é porque os dados já haviam sido carregados antes, então a tabela é apenas exibida novamente. Serializar e deserializar o schema deste dataSet será uma tarefa bem simples, mais que no caso anterior : O dataSet não precisa que utilizemos uma classe de serialização, como antes, ele próprio possui métodos para fazer a serialização do schema.
Neste ponto já conseguimos permitir que o usuário personalize a aplicação e que esta personalização seja mantida, o que já torna a aplicação bem interessante. Mas para que a aplicação fique ainda mais útil torna-se necessário permitir que a aplicação guarde não apenas uma, mas diversas configurações nomeadas. Assim sendo o usuário pode se conectar a diferentes bancos de dados e guardar as configurações de como deseja visualizar cada um. Vamos começar criando uma classe Configuracao para agregar os dados que cada configuracao deve ter. Uma configuração terá um nome, uma string de conexão e irá também gerar um código que será anexado aos nomes de arquivos que guardarão as características da configuração. Veja como fica : 152 <Serializable()> Public Class config 153 Public NomeConfig As String 154 Public Conexao As String 155 Private _id As String 156 157 Public Sub New() 158 159 End Sub 160 161 Public Sub New(ByVal cf As String, ByVal con As String, ByVal id As Integer) 162 NomeConfig = cf 163 Conexao = con 164 End Sub 165 166 Public ReadOnly Property CodigoArquivo() As String 167 Get 168 Return (NomeConfig.Substring(0, 3) & _id) 169 End Get 170 End Property 171 172 Public Overrides Function tostring() As String 173 Return (NomeConfig) 174 End Function 175 176 End Class Criei um construtor recebendo parâmetros, para simplificar a configuração desta classe. Uma propriedade readonly chamada CodigoArquivo, que irá gerar uma pequena string a ser anexada o nome dos arquivos. A função ToString levou um overrides para podermos definir o que desejamos que apareça em uma combo quando esta classe for inserida em uma combo. Vamos então preparar este formulário que temos para poder lidar com essa classe. Esse formulário irá receber uma instância desta classe como parâmetro quando for aberto (o usuário já terá escolhido a configuração - faremos isso depois) e deverá utilizar as informações contidas nesta classe. Primeiramente, uma propriedade para controlar o recebimento desta classe : 178 Public Property configuracao() As frmConfig.config 179 Get 180 Return (_Configuracao) 181 End Get 182 Set(ByVal Value As frmConfig.config) 183 _Configuracao = Value 184 End Set 185 End Property As propriedades que geram os nomes dos arquivos passarão a utilizar esta propriedade configuração, veja : 187 Private ReadOnly Property NomeSchema() As String 188 Get 189 Return (Application.StartupPath & "\schema" & Configuracao.CodigoArquivo & ".xml") 190 End Get 191 End Property 192 193 Private ReadOnly Property NomeHash() As String 194 Get 195 Return (Application.StartupPath & "\hash" & Configuracao.CodigoArquivo & ".bin") 196 End Get 197 End Property Podemos também exibir no título do form o nome da configuração selecionada : 59 Me.Text = Me.configuracao.NomeConfig Precisaremos de um botão que permita ao usuário trocar a configuração selecionada. Neste caso o ideal será transformarmos a inicialização do form (deserializações) e a finalização (serializações) em subs que possam ser chamadas de mais de um local. Veja como fica : 198 Sub inicializarForm() 199 Dim t As Type 200 201 salvarconfig() 202 203 Dim c As CurrencyManager 204 c = Me.BindingContext(p) 205 c.SuspendBinding() 206 207 ar.Clear() 208 p.Clear() 209 210 ds = New DataSet 211 lstObjetos.DataSource = Nothing 212 lstObjetos.Items.Clear() 213 214 Me.Text = Me.configuracao.NomeConfig 215 216 t = GetType(System.Data.OleDb.OleDbSchemaGuid) 217 p.AddRange(t.GetFields) 218 219 If File.Exists(NomeHash) Then 220 Dim obj As New BinaryFormatter 221 Dim st As New FileStream(NomeHash, FileMode.Open) 222 ar = obj.Deserialize(st) 223 st.Close() 224 For Each f As String In ar 225 remover(f) 226 Next 227 End If 228 229 If IO.File.Exists(NomeSchema) Then 230 ds.ReadXmlSchema(NomeSchema) 231 End If 232 233 234 lstObjetos.DataSource = p 235 lstObjetos.DisplayMember = "Name" 236 c.ResumeBinding() 237 End Sub 238 239 Public Sub salvarconfig() 240 ds.WriteXmlSchema(NomeSchema) 241 242 243 If File.Exists(NomeHash) Then 244 File.Delete(NomeHash) 245 End If 246 Dim obj As New BinaryFormatter 247 Dim st As New FileStream(NomeHash, FileMode.Create) 248 obj.Serialize(st, ar) 249 st.Close() 250 End Sub Desta forma podemos chamar essas subs do evento Load, closing, do botão e uma chama a outra (ao trocar a configuração, salva-se a anterior). Vamos então criar um 2o form, que permitirá ao usuário selecionar a configuração desejada ou criar uma nova configuração. A aplicação passará a ser disparada por uma sub main que interligará os dois forms, veja : 253 Module Module1 254 Public Sub Main() 255 Dim frm As New frmConfig 256 Dim f As New Form1 257 frm.ShowDialog() 258 If frm.DialogResult = DialogResult.OK Then 259 f.configuracao = frm.Configuracao 260 Application.Run(f) 261 End If 262 End Sub 263 End Module No formulário para escolha da configuração, vamos chama-lo de frmConfig, vamos inserir uma combo para listar as configurações existentes, um botão para disparar a criação de uma nova configuração e um botão para dar ok na seleção de uma configuração e seguir adiante.
Teremos mais um arquivo serializado, que conterá a lista de configurações existentes. Precisaremos no load do form fazer a deserialização deste arquivo. Veja como fica : 59 If File.Exists(arquivoConfig) Then 60 Dim obj As New BinaryFormatter 61 Dim st As New FileStream(arquivoConfig, FileMode.Open) 62 hs = obj.Deserialize(st) 63 st.Close() 64 End If 65 cmbConfigs.DataSource = hs 258 Public ReadOnly Property arquivoConfig() As String 259 Get 260 Return (Application.StartupPath & "/configs.bin") 261 End Get 262 End Property Quando um item na combo for selecionado, habilitamos o botão ok. Quando o botão Ok for clicado, atribuimos a classe selecionada na variável Configuracao. Veja como fica : 265 Public Configuracao As config 266 267 Private Sub cmbConfigs_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmbConfigs.SelectedIndexChanged 268 cmdOk.Enabled = True 269 End Sub 270 271 Private Sub cmdOk_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdOk.Click 272 Me.Configuracao = DirectCast(cmbConfigs.SelectedItem, config) 273 End Sub Quando o usuário clicar no botão para adicionar uma nova configuração precisaremos fazer os seguintes passos :
Veja como fica : 59 Dim nomeConfig As String 60 nomeConfig = InputBox("Informe o nome da nova configuração", "Nova Configuração") 61 62 If nomeConfig = "" Then 63 Exit Sub 64 End If 65 66 Dim dataLink As Object = Microsoft.VisualBasic.Interaction.CreateObject("DataLinks") 67 dataLink.hWnd = Me.Handle 68 69 Dim o As Object = dataLink.PromptNew() 70 If o Is Nothing Then 71 Exit Sub 72 Else 73 Dim cl As New config(nomeConfig, o.connectionstring.ToString, hs.Count) 74 hs.Add(cl) 75 Dim c As CurrencyManager 76 c = Me.BindingContext(hs) 77 c.SuspendBinding() 78 c.ResumeBinding() 79 80 End If Observe que neste código utilizei um truque para exibir para o usuário a própria janela de configuração de uma conexão do OLEDB, permitindo desta forma que o usuário crie a string de conexão em um ambiente já familiar a ele. Isso foi exposto em uma dica em http://www.bufaloinfo.com.br/dicas.asp?cod=430
Agora que chegamos neste ponto e a aplicação já está funcionando e guardando configurações diversas do usuário, vamos então inserir na aplicação a capacidade de fazer filtragens das informações exibidas. Vamos criar 2 tipos de filtragem : Uma filtragem simples, aplicada sobre os dados exibidos e uma filtragem relacionada, por exemplo, o usuário desejará ver os campos de uma determinada tabela. Vamos começar, claro, pela filtragem mais simples. Precisaremos criar um formulário novo através do qual o usuário possa definir a filtragem a ser realizada. Vamos chamar este novo formulário de frmRestricoes. Muitos objetos deste formulário deverão ser dinâmicos. Ele deverá exibir os campos existentes na DataTable que será filtrada (exibir mesmo campos que tenham sido excluidos pelo usuário) e permitir que o usuário digite, para cada campo, o critério de filtro, ou deixe o campo vazio. Assim sendo deverão ser criados labels e textboxes tantos quantos forem os campos da dataTable sendo filtrada. Mas isso se complica um pouco mais. As DataTables possuem os campos devolvidos pelo provider OLEDB, fornecendo informações sobre determinados tipos de objetos no banco. O problema é que alguns dos campos em cada DataTable são específicos de determinados data providers e, por isso, não podem ser usados como parte das restrições. As DataTables tem um conjunto de campos genéricos, estes podendo ser usados nas restrições, e um conjunto de campos específicos, que não podem. O número de campos que podem ou não serem utilizados nas restrições variam para cada DataTable. No endereço http://msdn.microsoft.com/library/default.asp?url=/library/en-us/oledb/htm/oledbschema_rowsets.asp encontrei documentação sobre quantas colunas de cada tipo de DataTable podem ser usadas como critério. Isso acaba sendo algo fixo, veja como codifiquei, para resolver o problema : 292 Private Sub DefinirRestricoes() 293 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Assertions, 3) 294 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Catalogs, 1) 295 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Character_Sets, 3) 296 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Check_Constraints, 3) 297 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Check_Constraints_By_Table, 6) 298 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Collations, 3) 299 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Column_Domain_Usage, 3) 300 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Column_Privileges, 6) 301 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Columns, 4) 302 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Constraint_Column_Usage, 7) 303 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Constraint_Table_Usage, 5) 304 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Foreign_Keys, 6) 305 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Indexes, 5) 306 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Key_Column_Usage, 7) 307 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Primary_Keys, 3) 308 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Procedure_Columns, 4) 309 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Procedure_Parameters, 4) 310 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Procedures, 4) 311 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Provider_Types, 2) 312 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Referential_Constraints, 3) 313 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Schemata, 3) 314 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Statistics, 3) 315 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Table_Constraints, 7) 316 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Table_Privileges, 5) 317 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Table_Statistics, 7) 318 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Tables, 4) 319 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Tables_Info, 4) 320 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Translations, 3) 321 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Usage_Privileges, 6) 322 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.View_Column_Usage, 3) 323 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.View_Table_Usage, 3) 324 hsNumRestricoes.Add(System.Data.OleDb.OleDbSchemaGuid.Views, 3) 325 End Sub Assim sendo, utilizei esta hashTable definida em hard-code para guardar estas definições, que são fixas, e utiliza-las no algorítimo em código. O formulário de restrições precisará receber o tipo de objeto para o qual estão sendo montadas as restrições. Também precisará receber uma instância da classe config (frmconfig.config), pois ela contém a string de conexão da base utilizada no momento. Como resultado o formulário irá gerar um array de strings para ser utilizado como critério para a DataTable selecionada. 327 Public TipoObjeto As System.Guid 328 Public Configuracao As frmConfig.config 329 330 Dim filtro() As String 331 332 333 Public Property ItensFiltro() As String() 334 Get 335 Return (filtro) 336 End Get 337 Set(ByVal Value As String()) 338 filtro = Value 339 End Set 340 End Property
Duas funcionalidades a mais deste form são importantes de ressaltar, antes de vermos o código : O formulário pode estar criando uma restrição, mas pode também estar editando uma restrição já existente. Neste 2o caso o formulário já terá recebido um array de strings que precisará ser preenchido nas textbox. A entrada no form pode ter sido realizada através do click com o botão direito sobre uma coluna específica. Neste caso é provável que o usuário deseje criar um filtro para essa coluna, portanto podemos já colocar o foco nela. Ao carregar o formulário precisaremos fazer os seguintes passos :
Veja como fica : 342 Private Sub frmRestricoes_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 343 Dim dt As DataTable 344 345 DefinirRestricoes() 346 347 If Not hsNumRestricoes.Contains(TipoObjeto) Then 348 MsgBox("Este elemento não aceita restricoes") 349 Me.DialogResult = DialogResult.Cancel 350 End If 351 352 cn.ConnectionString = Configuracao.Conexao 353 cn.Open() 354 dt = cn.GetOleDbSchemaTable(TipoObjeto, Nothing) 355 cn.Close() 356 357 For icnt As Integer = 0 To hsNumRestricoes(TipoObjeto) - 1 358 Dim dc As DataColumn 359 Dim l As New Label 360 dc = dt.Columns(icnt) 361 362 l.Text = dc.ColumnName 363 l.Top = y 364 l.Left = x1 365 l.Width = 190 366 Me.Controls.Add(l) 367 368 Dim t As New TextBox 369 t.Text = "" 370 t.Name = dc.ColumnName 371 t.Top = y 372 t.Left = x2 373 t.Width = 170 374 Me.Controls.Add(t) 375 arText.Add(t) 376 If UCase(dc.ColumnName) = UCase(nomeColuna) Then 377 ControleFoco = t 378 End If 379 y += t.Height + 15 380 Next 381 382 If Not IsNothing(filtro) AndAlso filtro.Length > 0 Then 383 For icnt As Integer = 0 To filtro.Length - 1 384 arText(icnt).text = filtro(icnt) 385 Next 386 End If 387 388 Me.Height = y + GroupBox1.Height + 50 389 Me.Top = 0 390 Me.Width = 415 391 392 End Sub
O código é bem simples, basicamente recursos de montagem dinâmica da interface. O botão cancelar deste form não precisa ter código, pode apenas estar configurado com o dialogResult Cancel. Já o botão Ok, além de estar configurado com o DialogResult Ok precisará montar um array de strings para gerar o critério. Veja como fica : 395 Private Sub cmdOk_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdOk.Click 396 ReDim filtro(arText.Count - 1) 397 For a As Integer = 0 To arText.Count - 1 398 If arText(a).text = "" Then 399 filtro(a) = Nothing 400 Else 401 filtro(a) = arText(a).text 402 End If 403 Next 404 End Sub Observem que no form_load foi feito um array de TextBox justamente para simplificar este processamento. Se a textbox estiver vazia atribuimos Nothing no array, caso contrário atribuimos o conteúdo da textbox. Feito isso precisamos controlar a chamada deste form de restrições a partir do form principal. Isso será feito com um item a mais no menu de contexto. Podemos também criar um ícone sobre a dataGrid, ícone que só apareça quando houverem filtros na dataGrid, e chamar este formulário a partir do duplo clique neste ícone. Ficamos então com 2 pontos de entrada, ambos deverão transmitir para o formulário de restrições o filtro existente, para que seja alterado. Veja como fica : 406 Private Sub PicFiltro_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles PicFiltro.DoubleClick 407 Dim frm As New frmRestricoes 408 409 exibirRestricoes(frm) 410 End Sub 411 412 Private Sub mnuRestricoes_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuRestricoes.Click 413 Dim hit As DataGrid.HitTestInfo 414 Dim frm As New frmRestricoes 415 hit = dg.HitTest(pt) 416 frm.nomeColuna = DirectCast(dg.DataSource, DataTable).Columns(hit.Column).ColumnName 417 418 419 exibirRestricoes(frm) 420 End Sub 421 422 Private Sub exibirRestricoes(ByVal frm As frmRestricoes) 423 frm.Configuracao = Me.configuracao 424 frm.TipoObjeto = DirectCast(lstObjetos.SelectedItem, FieldInfo).GetValue(GetType(System.Data.OleDb.OleDbSchemaGuid)) 425 426 If hsFiltros.Contains(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) Then 427 frm.ItensFiltro = hsFiltros(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) 428 End If 429 frm.ShowDialog() 430 If frm.DialogResult = DialogResult.OK Then 431 If hsFiltros.Contains(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) Then 432 hsFiltros(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) = frm.ItensFiltro 433 Else 434 hsFiltros.Add(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name, frm.ItensFiltro) 435 End If 436 End If 437 LerSchema(lstObjetos.SelectedItem) 438 End Sub Alguns detalhes são interessantes, devem ser observados :
A rotina LerSchema é a mesma que já haviamos criado anteriormente, mas agora verificando se existem ou não filtros para o item que está sendo lido. Veja como fica : 440 Private Sub LerSchema(ByVal nome As FieldInfo) 441 Dim dt As DataTable 442 CN.Open() 443 Try 444 If hsFiltros.Contains(nome.Name) Then 445 dt = CN.GetOleDbSchemaTable(nome.GetValue(GetType(System.Data.OleDb.OleDbSchemaGuid)), hsFiltros(nome.Name)) 446 PicFiltro.Visible = True 447 Else 448 dt = CN.GetOleDbSchemaTable(nome.GetValue(GetType(System.Data.OleDb.OleDbSchemaGuid)), Nothing) 449 PicFiltro.Visible = False 450 End If 451 Catch er As Exception 452 MsgBox("Este tipo de elemento não está disponível neste provider OLEDB" & vbCrLf & er.Message) 453 Exit Sub 454 Finally 455 CN.Close() 456 End Try 457 If Not ds.Tables.Contains(dt.TableName) Then 458 ds.Tables.Add(dt) 459 Else 460 ds.Tables(dt.TableName).Clear() 461 ds.Merge(dt, False, MissingSchemaAction.Ignore) 462 End If 463 End Sub Observe o sutil controle de exibição do ícone que indicará a existência de um filtro. Por fim, para completarmos esta tarefa, precisamos garantir que esta hashTable será serializada e deserializada junto com o restante das configurações. Então teremos mais uma propriedade para gerar o nome do arquivo e precisaremos alterar as rotinas inicializarForm e SalvarConfig que já haviamos feito, adicionando um pequeno trecho para serializar e deserializar. Veja como fica : 465 Private ReadOnly Property NomeFiltro() As String 466 Get 467 Return (Application.StartupPath & "\filtro" & configuracao.CodigoArquivo & ".bin") 468 End Get 469 End Property
InicializarForm : 59 If File.Exists(NomeFiltro) Then 60 Dim obj As New BinaryFormatter 61 Dim st As New FileStream(NomeFiltro, FileMode.Open) 62 hsFiltros = obj.Deserialize(st) 63 st.Close() 64 End If SalvarConfig : 59 If File.Exists(NomeFiltro) Then 60 File.Delete(NomeFiltro) 61 End If 62 63 Dim obj2 As New BinaryFormatter 64 Dim st2 As New FileStream(NomeFiltro, FileMode.Create) 65 obj2.Serialize(st2, hsFiltros) 66 st2.Close() Com isso completamos a filtragem básica, então vamos agora para a criação da filtragem mais avançada, relacionando dois tipos de objetos. A interface deverá ter o seguinte comportamento : O usuário visualiza um conjunto de dados, de um determinado tipo de objeto. Pega então outro tipo de objeto na listbox e arrasta até a dataGrid. Abre-se então a caixa de criação de filtros. Os campos listados com textboxes serão os do tipo de objeto que foi arrastado. Os dados deste tipo de objeto serão filtrados com base nos dados que estavam sendo exibidos na DataGrid (por exemplo, arrasta-se o tipo columns para cima da grid que exibia o tipo TableS_Info, as colunas serão filtradas com base no nome da tabela). Os campos existentes na dataGrid deverão ser mostrados em uma listbox na tela de construção do filtro. As textbox ficarão readonly. O usuário irá arrastar os campos da listbox para sobre a textbox, indicando quais campos da grid servirão de critério em quais campos do outro tipo de objeto.
Concluida a montagem do critério, será criado um novo item no menu de contexto. Quando o usuário clicar sobre um registro da grid com o botão direito, terá a opção de disparar este novo item. Então, por exemplo, poderá clicar com o botão direito sobre uma tabela e pedir para ver os campos desta tabela. Sendo que toda essa relação será configurável pelo próprio usuário. Com isso precisaremos criar uma adaptação em nosso frmRestricoes. Ele deverá ter 2 tipos de comportamento : Recebendo apenas uma identificação de campo ele terá o comportamento tradicional como montamos até agora, mas se receber 2 identificações de campo deverá exibir a listbox, definir as textbox como readonly e permitir o drag and drop. Teremos um 2o Guid de tipo de objeto : 457 Public _TipoObjetoDrop As System.Guid 458 459 Public Property TipoObjetodrop() As System.Guid 460 Get 461 Return (_TipoObjetoDrop) 462 End Get 463 Set(ByVal Value As System.Guid) 464 _TipoObjetoDrop = Value 465 End Set 466 End Property
O load do form, que faz todo o processamento de criação da interface, sofrerá sutis mudanças, observe : 469 Private Sub frmRestricoes_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 470 Dim dt As DataTable 471 472 DefinirRestricoes() 473 474 If Not hsNumRestricoes.Contains(TipoObjeto) Then 475 MsgBox("Este elemento não aceita restricoes") 476 Me.DialogResult = DialogResult.Cancel 477 End If 478 479 cn.ConnectionString = Configuracao.Conexao 480 cn.Open() 481 dt = cn.GetOleDbSchemaTable(TipoObjeto, Nothing) 482 cn.Close() 483 484 For icnt As Integer = 0 To hsNumRestricoes(TipoObjeto) - 1 485 Dim dc As DataColumn 486 Dim l As New Label 487 dc = dt.Columns(icnt) 488 489 l.Text = dc.ColumnName 490 l.Top = y 491 l.Left = x1 492 l.Width = 190 493 Me.Controls.Add(l) 494 495 Dim t As New TextBox 496 t.Text = "" 497 t.Name = dc.ColumnName 498 t.Top = y 499 t.Left = x2 500 t.Width = 170 501 Me.Controls.Add(t) 502 arText.Add(t) 503 If UCase(dc.ColumnName) = UCase(nomeColuna) Then 504 ControleFoco = t 505 End If 506 If Not TipoObjetoDrop.Equals(TipoObjetoDrop.Empty) Then 507 AddHandler t.DragOver, AddressOf dragSobre 508 AddHandler t.DragDrop, AddressOf dragCaiu 509 t.ReadOnly = True 510 t.AllowDrop = True 511 End If 512 y += t.Height + 15 513 Next 514 If Not IsNothing(filtro) AndAlso filtro.Length > 0 Then 515 For icnt As Integer = 0 To filtro.Length - 1 516 arText(icnt).text = filtro(icnt) 517 Next 518 End If 519 Me.Height = y + GroupBox1.Height + 50 520 Me.Top = 0 521 Me.Width = 415 522 523 If Not TipoObjetoDrop.Equals(TipoObjetoDrop.Empty) Then 524 Dim dt2 As DataTable 525 cn.Open() 526 dt2 = cn.GetOleDbSchemaTable(TipoObjetoDrop, Nothing) 527 cn.Close() 528 Dim arc As New ArrayList 529 arc.AddRange(dt2.Columns) 530 lstCampos.DataSource = arc 531 Me.Width = 736 532 End If 533 End Sub Observe as mudanças : A cada textbox criada verifica-se se o 2o tipo de objeto foi informado ou não. Se foi, altera-se a configuração da textbox para a 2a forma de atuação : vincula-se a textbox com o tratamento de drag-and-drop e define-se a textbox como readonly. Ao final, mais uma vez se o 2o tipo de objeto houver sido informado, preenche-se uma listbox com os campos da dataTable deste 2o tipo de objeto. Veja o código para habilitar o drag and drop : 536 Private Sub lstCampos_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles lstCampos.MouseDown 537 Dim pt2 As New Point(e.X, e.Y) 538 If e.Button = MouseButtons.Right Then 539 lstCampos.DoDragDrop(lstCampos.Items(lstCampos.IndexFromPoint(pt2)), DragDropEffects.Copy) 540 End If 541 End Sub 542 543 Private Sub dragSobre(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DragEventArgs) 544 e.Effect = DragDropEffects.Copy 545 End Sub 546 547 Private Sub dragCaiu(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DragEventArgs) 548 DirectCast(sender, TextBox).Text = DirectCast(e.Data.GetData(GetType(DataColumn)), DataColumn).ColumnName 549 End Sub Nada muda na montagem do Array, mudará na forma como é utilizado, mas não em sua montagem. Vejamos então como este form será disparado. Cada uma destas relações entre dois tipos de objetos irá gerar um item de menu. Então precisaremos de uma classe que represente cada item de menu. Veja como fica esta classe : 551 <Serializable()> Public Class ItemMenu 552 553 Public Campo As FieldInfo 554 555 Public Filtros() As String 556 557 Public ReadOnly Property NomeItem() As String 558 Get 559 Return ("Detalhes de " & Campo.Name) 560 End Get 561 End Property 562 563 Public Sub New(ByVal cmp As FieldInfo, ByVal flt() As String) 564 Campo = cmp 565 Filtros = flt 566 End Sub Observe que ela possui apenas um FieldInfo, a idéia é que esta classe será guardada de forma a estar ligada ao outro tipo de objeto, em uma relação pai-filho. Precisaremos então de mais uma hashTable para guardar os itens de menu. Cada tipo de objeto poderá ter itens de menu. Assim sendo esta hashTable terá um elemento para cada tipo de objeto. Mas este elemento poderá conter vários itens de menu, afinal um mesmo objeto poderá ter vários itens de menu. Assim sendo cada elemento desta hashTable deverá ser uma nova hashTable. Vamos chamar essa hashTable de hsDetalhes. Considerando isso, veja como fica este novo disparo do frmRestricoes : 572 Private Sub lstObjetos_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles lstObjetos.MouseDown 573 Dim pt2 As New Point(e.X, e.Y) 574 575 If e.Button = MouseButtons.Right And lstObjetos.IndexFromPoint(pt2) <> -1 Then 576 Dim r As DragDropEffects 577 r = lstObjetos.DoDragDrop(lstObjetos.Items(lstObjetos.IndexFromPoint(pt2)), DragDropEffects.Move) 578 Dim frm As New frmRestricoes 579 frm.TipoObjeto = DirectCast(lstObjetos.Items(lstObjetos.IndexFromPoint(pt2)), FieldInfo).GetValue(GetType(System.Data.OleDb.OleDbSchemaGuid)) 580 frm.TipoObjetodrop = DirectCast(lstObjetos.SelectedItem, FieldInfo).GetValue(GetType(System.Data.OleDb.OleDbSchemaGuid)) 581 movido = lstObjetos.Items(lstObjetos.IndexFromPoint(pt2)) 582 exibirRestricoesCampos(frm) 583 End If 584 End Sub 585 586 Private Sub exibirRestricoesCampos(ByVal frm As frmRestricoes) 587 frm.Configuracao = Me.configuracao 588 589 590 If hsDetalhes.Contains(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) Then 591 Dim arm As Hashtable 592 arm = hsDetalhes(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) 593 If arm.Contains(frm.TipoObjetodrop) Then 594 frm.ItensFiltro = DirectCast(arm(frm.TipoObjetodrop), ItemMenu).Filtros 595 End If 596 End If 597 598 frm.ShowDialog() 599 600 If frm.DialogResult = DialogResult.OK Then 601 If hsDetalhes.Contains(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) Then 602 Dim arm As Hashtable 603 arm = hsDetalhes(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) 604 If arm.Contains(frm.TipoObjetodrop) Then 605 arm(frm.TipoObjetodrop) = New ItemMenu(movido, frm.ItensFiltro) 606 Else 607 arm.Add(frm.TipoObjetodrop, New ItemMenu(movido, frm.ItensFiltro)) 608 End If 609 hsDetalhes(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) = arm 610 Else 611 Dim arm As New Hashtable 612 arm.Add(frm.TipoObjetodrop, New ItemMenu(movido, frm.ItensFiltro)) 613 hsDetalhes.Add(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name, arm) 614 End If 615 End If 616 LerSchema(lstObjetos.SelectedItem) 617 End Sub
Observe a sequencia de IFs controlando cuidadosamente o preenchimento dos arrayLists. Mais uma vez precisaremos de código para serializar e deserializar, veja como fica :
619 Private ReadOnly Property NomeDetalhes() As String 620 Get 621 Return (Application.StartupPath & "\detalhes" & configuracao.CodigoArquivo & ".bin") 622 End Get 623 End Property
Os itens de menu precisarão ser criados no menu de contexto sempre que este for exibido. Observe como fica : 626 Private Sub ContextMenu1_Popup(ByVal sender As Object, ByVal e As System.EventArgs) Handles ContextMenu1.Popup 627 If hsDetalhes.Contains(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) Then 628 Dim arm As Hashtable 629 arm = hsDetalhes(DirectCast(lstObjetos.SelectedItem, FieldInfo).Name) 630 For icnt As Integer = ContextMenu1.MenuItems.Count - 1 To 2 Step -1 631 ContextMenu1.MenuItems.RemoveAt(icnt) 632 Next 633 For Each di As DictionaryEntry In arm 634 Dim m As New SubMenuItem 635 m.Text = DirectCast(di.Value, ItemMenu).NomeItem 636 m.menu = di.Value 637 AddHandler m.Click, AddressOf AberturaDetalhes 638 ContextMenu1.MenuItems.Add(m) 639 Next 640 End If 641 End Sub Observe que para não duplicar os itens de menu e garantir que estejam sempre atualizados foi necessário eliminar os itens e re-criados a cada vez que o menu é exibido. A criação dinâmica implica em atribuição dinâmica do tratador de evento, o que não é surpresa. Observe que não usei a classe menuItem do .NET, mas sim uma classe chamada subMenuItem. Isso porque precisava lervar informações adicionais junto ao item de menu, então criei esta classe derivando de menuItem. Veja como ficou : 643 Public Class SubMenuItem 644 Inherits MenuItem 645 Dim _menu As ItemMenu 646 647 Public Property menu() As ItemMenu 648 Get 649 Return _menu 650 End Get 651 Set(ByVal Value As ItemMenu) 652 _menu = Value 653 End Set 654 End Property 655 End Class Por fim, veja o tratamento do click no item : 657 Private Sub AberturaDetalhes(ByVal sender As System.Object, ByVal e As System.EventArgs) 658 Dim frm As New frmDetalhes 659 frm.configuracao = Me.configuracao 660 frm.filtros = DirectCast(sender, SubMenuItem).menu.Filtros 661 frm.TipoObjeto = DirectCast(sender, SubMenuItem).menu.Campo 662 frm.DadosFiltro = DirectCast(dg.DataSource, DataTable).Rows(dg.CurrentRowIndex) 663 frm.ShowDialog() 664 End Sub Chamamos neste caso mais um form para que sejam exibidas as informações de detalhe. Vamos criar este novo form, frmDetalhes. Este novo form conterá apenas uma DataGrid. 666 Public TipoObjeto As FieldInfo 667 Public filtros() As String 668 Public DadosFiltro As DataRow 669 Public configuracao As frmConfig.config 670 671 Dim flt() As String 672 673 674 Private Sub frmDetalhes_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 675 Dim dt As DataTable 676 PrepararFiltros() 677 CN.ConnectionString = configuracao.Conexao 678 CN.Open() 679 dt = CN.GetOleDbSchemaTable(TipoObjeto.GetValue(GetType(System.Data.OleDb.OleDbSchemaGuid)), flt) 680 CN.Close() 681 DG.DataSource = dt 682 End Sub 683 684 Sub PrepararFiltros() 685 ReDim flt(filtros.Length - 1) 686 For icnt As Integer = 0 To filtros.Length - 1 687 If IsNothing(filtros(icnt)) Then 688 flt(icnt) = Nothing 689 Else 690 flt(icnt) = DadosFiltro(filtros(icnt)).ToString 691 End If 692 Next 693 End Sub Repare que este novo form recebe uma dataRow, a datarow em que o cursor está na dataGrid inicial. Utiliza-se desta dataRow para montar os parâmetros que serão usados na obtenção dos novos dados. Assim sendo, por exemplo, ao clicar com o botão direito sobre uma tabela pode-se obter a lista de suas colunas.
Observações
Conclusão Utilizando o ADO.NET temos muitos recursos para obter informações sobre a estrutura da base de dados. 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 (21) 9240-5134 (21) 9240-7281 e-Mail: contato@bufaloinfo.com.br