C# - Vinculando uma fonte de dados XML a um controle TreeView


Imagine o seguinte cenário:

Você trabalha em uma grande empresa que recebe dados das filiais para serem consolidados. Os dados sempre estiveram em arquivos texto que você tratava e consolidava.

A partir do mês que vem os dados passarão a vir no formato XML e você terá que acessar e exibir estas informações.

Você receberá dois arquivos : um arquivo XML contendo os dados propriamente ditos e um arquivo XSD usado para validar o arquivo XML.

Não devemos confundir métodos para validar um documento XML com esquemas usados para esta finalidade. Atualmente existem três tipos de schemas usados para validar XML: DTD (Document Type Definition), XDR Schemas (XML-Data Reduced) e XSD Schemas (XML-Schema Definition)
  • DTD – Método inicial de validação de XML descrito no W3C XML (www.w3.org/). A recomendação atual concedida ao XSD tornou os DTD obsoletos.
  • XDR – Tecnologia criada pela Microsoft. similar ao XSD. Tem a vantagem de ter sido escrito usando XML.
  • XSD – É e recomendação atual do W3C. Substitui os DTDs e os XRDs da Microsoft.

O arquivo XML chamado de estoque.xml possui a seguinte estrutura:

<?xml version="1.0" standalone="yes"?>
<NewDataSet>
<Produtos>
<
ProdutoID>355</ProdutoID>
<CategoriaID>16</CategoriaID>
<NumeroModelo>RU007</NumeroModelo>
<NomeModelo>Rain Racer 2000</NomeModelo>
<ImagemProduto>image.gif</ImagemProduto>
<CustoUnitario>1499.99</CustoUnitario>
<Descricao>Looks like an ordinary bumbershoot, but don't be fooled! Simply place Rain Racer's tip on the ground and press the release latch.</Descricao>

</Produtos>
.................
<Produtos>
</Produtos>
<Categorias>
<CategoriaID>14</CategoriaID>
<NomeCategoria>Communications</NomeCategoria>
</Categorias>
.......................
<Categorias>
<CategoriaID>20</CategoriaID>
<NomeCategoria>General</NomeCategoria>

</Categorias>
</NewDataSet>

Este arquivo contém informações sobre produtos e categorias e o arquivo estoque.xsd que irá validar este arquivo e definir a sua estrutura e o relacionamento entre as tabelas possui o seguinte conteúdo:

<?xml version="1.0" standalone="yes"?>
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="NewDataSet" msdata:IsDataSet="true">
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element name="Produtos">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ProdutoID" msdata:ReadOnly="true" msdata:AutoIncrement="true" type="xs:int" />
              <xs:element name="CategoriaID" type="xs:int" />
              <xs:element name="NumeroModelo" minOccurs="0">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:maxLength value="50" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:element>
              <xs:element name="NomeModelo" minOccurs="0">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:maxLength value="50" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:element>
              <xs:element name="ImagemProduto" minOccurs="0">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:maxLength value="50" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:element>
              <xs:element name="CustoUnitario" type="xs:decimal" />
              <xs:element name="Descricao" minOccurs="0">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:maxLength value="3800" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Categorias">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="CategoriaID" msdata:ReadOnly="true" msdata:AutoIncrement="true" type="xs:int" />
              <xs:element name="NomeCategoria" minOccurs="0">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:maxLength value="50" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
    <xs:unique name="Constraint1" msdata:PrimaryKey="true">
      <xs:selector xpath=".//Produtos" />
      <xs:field xpath="ProdutoID" />
    </xs:unique>
    <xs:unique name="Categorias_Constraint1" msdata:ConstraintName="Constraint1" msdata:PrimaryKey="true">
      <xs:selector xpath=".//Categorias" />
      <xs:field xpath="CategoriaID" />
    </xs:unique>
  </xs:element>
</xs:schema>

Seu objetivo será 'destrinchar' estes arquivos, obter as informações e exibi-las em uma controle TreeView em uma aplicação Windows Forms.

Elementar não é mesmo ???

No arquivo estoque.xsd temos a definição de um dataset contendo as tabelas Categorias e Produtos e do relacionamento entre elas enquanto que no arquivo estoque.xml temos os dados.

A aplicação Windows Forms deverá exibir os dados conforme a figura abaixo:

Em um controle TreeView temos exibidos as categorias e os respectivos produtos
relacionados.

Ao selecionar um produto é exibido em um controle Label a direita a sua descrição.

E para terminar a fase de definições, você deve usar a linguagem C#.

Eu vou usar o Visual Studio 2010 beta 2 (estou usando e abusando) e criar um projeto Windows Forms Application (Menu File -> New Project) usando a linguagem C# com o nome TreeViewDataBinding;

No formulário padrão form1.cs defina o leiaute conforme mostrado na figura abaixo:

Agora no formulário form1.cs defina o namespace System.Data pois vamos trabalhar com objetos Table, DataSet, DataRow, etc.;

using System.Data;

No formulário vamos criar também algumas classes para gerenciar as informações que serão obtidas a partir do arquivo XML e XSD.

Eu poderia ter criado as classes em um arquivo separado (seria mais aconselhável) mas resolvi criar no formulário mesmo para mostrar que podemos usar este recurso.

A seguir temos o código das classes:

	public class ProdutoDataBase
	{
		public class Tabelas
		{        //define as tabelas que iremos usas
			public const string Produto = "Produtos";
			public const string Categoria = "Categorias";
		}

		public class ProdutoCampo
		{       //define os campos dos produtos que serão exibidos
			public const string Nome = "NomeModelo";
			public const string Descricao = "Descricao";
		}

		public class CategoriaCampo
		{      //define o campo da categoria que será exibido
			public const string Nome = "NomeCategoria";
		}

                               //define o dataSet e o DataRelation para o relacionamento das tabelas 
		private DataSet dsEstoque;
		DataRelation relCategoriaProduto;

		public ProdutoDataBase()
		{        //cria uma instância do DataSet
			dsEstoque = new DataSet();

	             try
           	             {
	                //lê o esquema e o arquivo XML que é a fonte de dados
	                dsEstoque.ReadXmlSchema(Application.StartupPath + "\\estoque.xsd");
	                dsEstoque.ReadXml(Application.StartupPath + "\\estoque.xml");

	                // Define o relacionamento entre Categorias e Produtos
	                relCategoriaProduto = new DataRelation("Prod_Cat",
	                    dsEstoque.Tables["Categorias"].Columns["CategoriaID"],
	                    dsEstoque.Tables["Produtos"].Columns["CategoriaID"]);
	                dsEstoque.Relations.Add(relCategoriaProduto);
           	            }
	             catch (Exception ex)
	             {
           	        MessageBox.Show(" Erro - A aplicação será encerrada : " + ex.Message);
	                Application.Exit();
	            }
	        }

                    //obtem a tabela Categorias
	     public DataTable getCategorias()
	     {
	 	return dsEstoque.Tables["Categorias"];
	     }

                    //obtem o produto relacionado com a categoria
	    public DataRow[] getProdutosNaCategoria(DataRow rowParent)
	    {
	  	return rowParent.GetChildRows(relCategoriaProduto);
	    }

                   //exibe a descrição do produto selecionado
	   public string getExibeTexto(DataRow row)
	   {
		string text = "";
		switch (row.Table.TableName)
		{
			case Tabelas.Produto:
				text = "ID: " + row[0] + "\n";
				text += "Nome: " + row[ProdutoCampo.Nome] + "\n\n";
				text += row[ProdutoCampo.Descricao];
				break;
		}
		return text;
	}
	}

Na classe ProdutoDataBase temos definidos as seguintes classes:

Nesta classe estamos acessando os arquivos XML e XSD usando do método ReadXML do objeto DataSet além de dar o suporte para acessar os dados do arquivo XML.

Agora no início do formulário vamos criar uma instância dessa classe para usar no acesso e exibição do dados.

private ProdutoDataBase DataClass = new ProdutoDataBase();

A seguir no evento Load do formulário temos o código que acessa os dados no XML da tabela Categorias e as exibe no controle TreeView;

private void Form1_Load(object sender, System.EventArgs e)
{
	TreeNode noPai;
	foreach (DataRow row in DataClass.getCategorias().Rows)
	{
		// Incluir o Nó Categoria
		noPai = treeDB.Nodes.Add(row[ProdutoDataBase.CategoriaCampo.Nome].ToString());
		noPai.ImageIndex = 0;
		// Armazena a informação sobre a Categoria
		noPai.Tag = row;
		// Inclui um Nó 
		noPai.Nodes.Add("*");
	}
}

A seguir no evento BeforeExpand do controle TreeView temos o código que armazena a informação sobre o  produto:

private void treeDB_BeforeExpand(object sender, System.Windows.Forms.TreeViewCancelEventArgs e)
{
	TreeNode nodeSelected, nodeChild;
	nodeSelected = e.Node;
	if (nodeSelected.Nodes[0].Text == "*")
	{
	   	// Limpa o Nó *
		nodeSelected.Nodes.Clear();
		foreach (DataRow row in
	   	    DataClass.getProdutosNaCategoria((DataRow)nodeSelected.Tag))
  		    {
			nodeChild = nodeSelected.Nodes.Add(row[ProdutoDataBase.ProdutoCampo.Nome].ToString());
			// Armazena a informação sobre Produto
			nodeChild.Tag = row;
			nodeChild.ImageIndex = 1;
			nodeChild.SelectedImageIndex = 1;
		   }
	 }
}

No evento AfterSelect do TreeView temos o código que exibe a informação sobre  o produto no controle Label - lblinfo:

private void treeDB_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e)
{
     lblInfo.Text = DataClass.getExibeTexto((DataRow)e.Node.Tag);
}

O código a seguir fecha a aplicação:

private void cmdClose_Click(object sender, System.EventArgs e)
{
    this.Close();
}

Dessa forma exibimos os dados de um arquivo XML em um controle TreeView.

Pegue o projeto completo aqui : TreeViewDataBinding.zip

Eu sei é apenas C#, mas eu gosto...

Referências:

  • VB .NET - Usando o controle TreeView

  • VB. NET - Herança - criando controles herdados

  • Usando TreeView na Pratica II

  • VB - Exibindo tabelas e registros em um conntrole TreeView

  • Usando o controle TreeView III

  • Lendo um arquivo XML em um TreeView

  • TreeView - Arrastar e Soltar (Drag and Drop)

  • VB.NET - 'Espiando' o sistema de arquivos

  • Criar banco de dados via Código - Access , SQL Server

  • Tabelas: Trabalhando com tabelas (Access).

  • Criando um Banco de dados com o VisData

  • Criando banco de dados e tabelas com o Access


  • José Carlos Macoratti