C# - Data Binding - Sincronização com Mestre-Detalhes


Este artigo mostra como usar a vinculação de dados em um formulário Windows Forms usando a linguagem C#.

A vinculação de dados fornece aos programadores uma forma de criar vínculos de leitura/escrita entre os controles da interface (Windows Forms) e a aplicação (modelo de dados).

A vinculação de dados em aplicações Windows Forms permite acessar os dados no banco de dados da aplicação bem como estruturas como arrays, e coleções.

Cada formulário Windows possui no mínimo um objeto BindingContext que gerencia os objetos CurrencyManager para o formulário. Para cada fonte de dados no formulário windows existe um único objeto CurrencyManager. Como podem existir múltiplas fontes de dados associadas em um formulário windows , o objeto BindingContext permite retornar qualquer objeto CurrencyManager particular associado com uma fonte de dados.

A classe CurrencyManager gerencia uma lista de objetos vinculados e deriva da classe BindingManagerBase.  Você usa o BindingContext para retornar um CurrencyManager ou PropertyManager.

A figura abaixo mostra de este cenário:

O CurrencyManager é usado para manter os controles de dados vinculados sincronizados entre si, e, realiza esta tarefa através do gerenciamento uma coleção de dados vinculados fornecidos por uma fonte de dados.

Uma propriedade importante do CurrencyManager é a propriedade Position. Currency é o termo usado para se referir a posição atual na estrutura de dados. Assim você pode usar a propriedade Position para determinar a posição atual de todos os controles vinculados ao mesmo CurrencyManager.

Em um formulário Windows você pode vincular a uma variedade de estruturas , desde um simples array até linhas de dados, visão de dados (DataTable, DataSet, DataView, DataColumn, etc.)  e assim por diante. A única exigência é que a estrutura suporte a interface IList.

Para exemplificar vou mostrar uma aplicação de banco de dados em um cenário muito comum onde desejamos visualizar um registro com um grupo de registros relacionados.  Ex: Um Cliente com seus pedidos ; Cada pedido com seu detalhe, etc.

Vamos então usar o banco de dados Northwind.mdb (poderíamos usar o Northwind.mdf) para exibir os dados de um cliente e seus pedidos relacionados sendo que cada pedido também exibe o detalhe relacionado.

Para entender como isso é possível devemos mostrar a estrutura das tabelas envolvidas do banco de dados Northwind.mdb:

Temos os seguintes relacionamentos:

Vamos então criar um novo projeto do tipo Windows Application no Visual C# 2008 Express Editon chamado MestreDetalhes;

A seguir renomeie o formulário padrão form1.cs para MestreDetalhe.cs e define o leiaute conforme a figura a seguir:

Temos 3 controles GroupBox:

1- Customer - Onde iremos exibir os dados do cliente - Nome, Contato, Fone e Fax exibidos em TextBox;

2- Orders - Onde iremos mostrar os pedidos do cliente selecionado em um DataGrid;

3- Order Details - Onde iremos exibir os detalhes de um pedido selecionado em um DataGrid;

É importante salientar que os controles deverão estar sincronizados de forma que ao navegarmos pelos clientes os pedidos e detalhes dos pedidos estarão relacionados;

Os controles Button efetuam a navegação para frente e para trás;

Ao executarmos o projeto devermos obter o seguinte resultado:

Agora vemos o código usado para obter o resultado:

		public MestreDetalhes()
		{
			// Cria os componentes
			InitializeComponent();

			// Define a conexão com o banco de dados
			// No exemplo estou usando Ms Access mas deixei a string para conexao com SQL Server
			//ConnectionString = "data source=xeon;uid=sa;password=manager;database=northwind";
			ConnectionString="Provider=Microsoft.JET.OLEDB.4.0;data source=c:\\dados\\Northwind.mdb";
			OleDbConnection cn = new OleDbConnection(ConnectionString);

			// cria o dataset
			ds = new DataSet("CustOrders");

			// Preenche o Dataset com os dados da tabela Customers,
			// mapeia o nome padrão da tabela
			// "Table" para "Customers".
			OleDbDataAdapter da1 = new OleDbDataAdapter("SELECT * FROM Customers",cn);
			da1.TableMappings.Add("Table","Customers");
			da1.Fill(ds);
			
			// Preenche o Dataset com os dados da tabela Orders,
			// mapeia o nome padrão da tabela
			// "Table" to "Orders".			
			OleDbDataAdapter da2 = new OleDbDataAdapter("SELECT * FROM Orders",cn);
			da2.TableMappings.Add("Table","Orders");
			da2.Fill(ds);

			// Preenche o Dataset com os dados da tabela Order Details,
			// mapeia o nome padrão da tabela
			// "Table" to "OrderDetails".			
			OleDbDataAdapter da3 = new OleDbDataAdapter("SELECT * FROM [Order Details]",cn);
			da3.TableMappings.Add("Table","OrderDetails");
			da3.Fill(ds);

			// Define o relacionamento "RelCustOrd" 
			// entre Customers ---< Orders
			System.Data.DataRelation relCustOrd;
			System.Data.DataColumn  colMaster1;
			System.Data.DataColumn  colDetail1;
			colMaster1 = ds.Tables["Customers"].Columns["CustomerID"];
			colDetail1 = ds.Tables["Orders"].Columns["CustomerID"];
			relCustOrd = new System.Data.DataRelation("RelCustOrd",colMaster1,colDetail1);
			ds.Relations.Add(relCustOrd);

			// Define o relacionamento "RelOrdDet" 
			// entre Orders ---< [Order Details]
			System.Data.DataRelation relOrdDet;
			System.Data.DataColumn  colMaster2;
			System.Data.DataColumn  colDetail2;
			colMaster2 = ds.Tables["Orders"].Columns["OrderID"];
			colDetail2 = ds.Tables["OrderDetails"].Columns["OrderID"];
			relOrdDet = new System.Data.DataRelation("RelOrdDet",colMaster2,colDetail2);
			ds.Relations.Add(relOrdDet);
 
			// O DataViewManager retornado pela propriedade DefaultViewManager
			// permite criar definições customizadas para cada
			// DataTable no DataSet.
			dsView = ds.DefaultViewManager;

			// Vinculando com o Grid's
			grdOrders.DataSource = dsView;
			grdOrders.DataMember = "Customers.RelCustOrd";

			grdOrderDetails.DataSource = dsView;
			grdOrderDetails.DataMember = "Customers.RelCustOrd.RelOrdDet";
      
                                       //vinculando o combobox
			cbCust.DataSource = dsView;
			cbCust.DisplayMember = "Customers.CompanyName";
                                      cbCust.ValueMember = "Customers.CustomerID";

			// vinculando com os controles TextBox
			txtContact.DataBindings.Add("Text",dsView,"Customers.ContactName");
			txtPhoneNo.DataBindings.Add("Text",dsView,"Customers.Phone");
			txtFaxNo.DataBindings.Add("Text",dsView,"Customers.Fax");
		}

Embora o código esteja comentado vejamos as etapas definidas:

O código dos botões de navegação é dado a seguir e usa a propriedade Position para navegar pelos registros;

		// posiciona no registro anterior na tabela customer
		private void btnPrev_Click(object sender, System.EventArgs e)
		{
			if (this.BindingContext[dsView,"Customers"].Position > 0) 
			{
				this.BindingContext[dsView,"Customers"].Position--;
			}
		}

		// Posicona no proximo registro na tabela Customer
		private void btnNext_Click(object sender, System.EventArgs e)
		{
			CurrencyManager cm = (CurrencyManager)this.BindingContext[dsView,"Customers"];
			if (cm.Position < cm.Count - 1) 
			{
				cm.Position++;
			}
		}

Se você tem dois controles vinculados no mesmo datasource   e não deseja compartilhar a mesma posição,  você deve ter certeza de que o membro BindingContext  de um controle é diferente do membro do outro controle.  Se eles tiverem  o mesmo BindingContext,  eles irão compartilhar a mesma posição no datasource.

Se você inclui um ComboBox e um DataGrid no formulário (como no nosso exemplo) , o padrão  é que o membro de cada um dos controles seja definido no BindingContext do formulário; Assim o padrão e que os controles irão compartilhar o mesmo BindingContext  e dai a seleção na combobox estará sincronizada com a linha atual do DataGrid

Se você não deseja este comportamento deverá criar um novo membro BindingContext  para um dos controles.

Pegue o projeto completo aqui : Mestre-Detalhes.zip

Até o próximo artigo C#.

Referências:


José Carlos Macoratti