msdn05_capa.JPG

Clique aqui para ler todos os artigos desta edição

 

Aplique Table Style e Column Style no DataGrid

por Kristy K. Saunders

 

Um dos mais persistentes desafios na criação de interfaces de usuário é definir como exibir grandes quantidades de dados de maneira eficiente e intuitiva, sem confundir o usuário. O problema torna-se particularmente complexo quando a interface deve refletir relacionamentos hierárquicos acerca dos dados que o usuário precisa modificar. O controle DataGrid do Windows Forms dá ao desenvolvedor uma ferramenta poderosa e flexível para enfrentar esse desafio. Este artigo explica suas operações básicas e mostra como permitir que o DataGrid exiba colunas de dados de maneira apropriada em aplicativos.

 

O DataGrid é um dos controles mais poderosos do Windows® Forms. Usando as configurações padrão das propriedades do DataGrid, você pode facilmente vincular um DataSet a um DataGrid para criar interfaces de usuário com o Windows Form bastante impressionantes—dessas que permitem que o usuário percorra níveis múltiplos de dados hierárquicos e classifique e ordene as colunas. Embora seja fácil começar a trabalhar com o controle DataGrid, seus recursos mais sofisticados—por exemplo, a criação de colunas personalizadas—continuam pouco documentados e exigem alguma tentativa e erro. Neste artigo explicarei como construir um DataGrid, ilustrarei os recursos básicos do controle e mostrarei como obter um controle abrangente sobre a aparência e o comportamento de tabelas e colunas. Finalmente, demonstrarei como é possível ampliar o controle DataGrid com objetos de estilo de coluna personalizados, por meio da implementação de um tipo que exibe valores em um Dropdown Combobox.

Aplicativo de quadro de horários (Timesheet)

Para ilustrar diversos recursos dos controles do DataGrid, este artigo analisará a implementação de um aplicativo de quadro de horários de uma firma fictícia de consultoria de software. Analise a Figura 1 para ver o aplicativo completo em funcionamento.

 

image001.gif 

Figura 1 Aplicativo de quadro de horários

 

A UI é um aplicativo Windows Forms que contém três controles DataGrid (Figura 2). O primeiro controle oferece uma listagem de todos os clientes somente para leitura. O segundo oferece uma listagem de todos os projetos associados ao cliente selecionado também somente para leitura. O terceiro controle DataGrid exibe as entradas do quadro de horas de um funcionário a serem cobradas no projeto selecionado. O DataGrid permite que o usuário insira e edite as entradas de horas, mas o usuário não pode modificar os itens nos controles DataGrid de cliente e projeto.

 

 

image002.gif 

Figura 2 Relacionamentos

 

Como carregar o DataSet

Os controles do DataGrid no aplicativo de quadro de horários vinculam um único objeto DataSet. Embora os DataGrids possam ser preenchidos a diversos tipos, os DataSets são suas fontes de dados mais comuns. O objeto DataSet do quadro de horário é preenchido a partir de um banco de dados, por meio de quatro objetos SqlDataAdapter. A criação e a configuração dos objetos SqlDataAdapter é simples por meio do assistente (wizard) de configuração do DataAdapter do Visual Studio® .NET. Observe que uso Stored Procedures com os objetos SqlDataAdapter para acessar as linhas das tabelas Client, Project, TimeEntry e Activity, e para inserir, atualizar e excluir linhas da tabela TimeEntry. Isso porque o aplicativo de quadro de horários usa colunas de identidade (ID – primary key) geradas pelo banco de dados—uma prática comum. Após uma operação de inclusão (Insert), esse valor gerado pelo banco de dados deve ser associado de volta à coluna correspondente no DataTable para operações subseqüentes naquela linha. O método mais confiável de obter esse valor é por meio de uma Stored Procedure INSERT, que o retorna o valor como um parâmetro de saída.

O código no manipulador de evento Load do formulário, mostrado na Listagem 1, preenche o DataSet quando o aplicativo é iniciado. Observe que a propriedade MissingSchemaAction do objeto SqlDataAdapter da entrada de hora é definida como MissingSchemaAction.AddWithKey. Isso provoca o retorno de mais informações de esquema, como informações de chave primária, informações de constraint e propriedades de colunas, inclusive se são permitidos valores null. Por padrão, o método Fill obtém somente metadados de nomes de colunas. Quando o aplicativo permitir que os usuários adicionem e atualizem linhas em um objeto do DataTable, as informações adicionais do DataTable ajudarão a detectar valores inválidos e violações de constraint.

 

Listagem 1 Carregando as quatro tabelas do DataSet

private void Form1_Load(object sender, System.EventArgs e)

{

  try

  {

    //Preenche os DataAdapters client e project

    //com as tabelas clients e projects

    sqlDataAdapterClients.Fill(dataSetTimeTracker,

    "Client");

    sqlDataAdapterProjects.Fill(dataSetTimeTracker,

    "Project");

    //Parâmetro EmployeeID da Stored

    //Procedure_GetTimeEntries

    sqlSelectCommand3.Parameters["@EmployeeID"].Value = 1;

    //Define AddWithKey para pegar a chave primária

    sqlDataAdapterEntries.MissingSchemaAction =

    MissingSchemaAction.AddWithKey;

    //Preenche o DataAdapter TimeEntry

    sqlDataAdapterEntries.Fill(dataSetTimeTracker,

    "TimeEntry");

    // Preenche o DataAdapter Activity

    sqlDataAdapterActivities.Fill(dataSetTimeTracker,

    "Activity");

  }

  catch (System.Exception ex)

  {

      MessageBox.Show(ex.Message, Text);

  }       

}

 

Como exibir dados do cliente no controle DataGrid

Após preencher um objeto DataSet, é simples obter um DataGrid para exibir os dados. Para fazer isso, utiliza-se o método SetDataBinding do DataGrid para especificar que DataSet e que DataTable dentro do DataSet serão usados. O código a seguir vincula a tabela Client no DataSet ao objeto dataGridClients (veja a Figura 3):

 

// Vincula a tabela do cliente ao DataGrid do cliente

dataGridClients.SetDataBinding(dataSetTimesheet, "Client");

 

image003.gif

Figura 3 Exibe a tabela Client no DataGrid

 

Depois que o objeto DataTable for vinculado ao DataGrid, o DataGrid usará os metadados da coluna para que a tabela Client defina os cabeçalhos das colunas, depois usará os dados da tabela Client para preencher o DataGrid. Neste ponto, você poderá clicar nos cabeçalhos da coluna para classificar por coluna. Embora isso seja bastante básico, logo você verá como executar truques mais sofisticados com um DataGrid.

Relações pai-filho em um DataSet

Você viu na Figura 2 que há um relacionamento pai-filho entre as tabelas Client e Project baseada na coluna ClientID. Além disso, a coluna ProjectID define o relacionamento pai-filho entre as tabelas Project e TimeEntry. Muito embora as tabelas do DataSet contenham as colunas ClientID e ProjectID, o DataSet não está ciente dessas relações. Para relacionar tabelas em um Dataset é preciso definir explicitamente o relacionamento, adicionando um objeto DataRelation a coleção Relations do DataSet. O aplicativo de quadro de horários (timesheet) usa duas instruções para criar os relacionamentos Client-Project e Project-TimeEntry.

 

dataSetTimesheet.Relations.Add("Client2Projects",

dataSetTimesheet.Tables["Clients"].Columns["ClientID"],

dataSetTimesheet.Tables["Projects"].Columns["ClientID"]);

 

dataSetTimesheet.Relations.Add("Project2TimeEntries",

dataSetTimesheet.Tables["Projects"].Columns["ProjectID"],

dataSetTimesheet.Tables["TimeEntry"].Columns["ProjectID"]);

 

Para adicionar um relacionamento, você informa um nome para a DataRelation ("Client2Projects" e "Project2TimeEntries" no exemplo) e especifica a coluna nas tabelas pai e filho que relacionarão as duas tabelas.

 

image004.gif

Figura 4 DataGrid com relacionamentos

 

A Figura 4 mostra o controle DataGrid depois que esses objetos DataRelation foram definidos. Observe que o nome do relacionamento filho ("Client2Projects") exibe um link sob cada linha quando o sinal de adição (+) é expandido. Um clique no primeiro link leva até o “filho” do cliente Woodgrove Bank na tabela Projects (Figura 5).

 

image005.gif

Figura 5 “Filhos” nos projetos

 

Você pode clicar no botão de voltar no caption do DataGrid para retornar à tabela-pai. O botão da extrema direita permite que você alterne a exibição da linha-pai. Observe que os links de "Project2TimeEntries" são exibidos quando um ícone "+" é expandido.

Embora o suporte interno à navegação seja útil, o modelo de detalhamento (drill-down) pode não oferecer a exibição ideal das informações hierárquicas do seu aplicativo. Uma alternativa é usar vários DataGrids para exibir dados relacionados. Para exibir uma tabela contendo apenas as linhas-filho de uma tabela pai que tenha sido selecionada em outro controle DataGrid, você pode vincular o controle DataGrid a um nome de relacionamento, em vez de a um objeto DataTable. Você define um relacionamento informando o nome da tabela-pai seguido pelo nome do relacionamento, delimitado por um ponto. Observe que é possível vincular vários níveis de DataRelations. Por exemplo, no aplicativo do quadro de horários, "Client.Clients2Project" especifica o objeto de DataRelation de Clients2Project e "Client.Clients2Project.Project2TimeEntries" especifica o objeto de DataRelation de Project2TimeEntries. A vinculação (Binding) a esse relacionamento retornará as linhas-filha do projeto que estava selecionado no cliente selecionado.

O código a seguir vincula o primeiro DataGrid do quadro de horários à tabela Clients, o DataGrid superior direito à relação "Clients.Clients2Project" e o DataGrid inferior a Clients.Clients2Project.Project2TimeEntries:

 

//Vincula a tabela Cliente ao DataGrid do cliente

dataGridClients.SetDataBinding(dataSetTimesheet,

   "Client");

//Vincula Client2Project ao datagrid do projeto

dataGridProjects.SetDataBinding(dataSetTimesheet,

   "Client.Client2Projects");

//Vincula Project2TimeEntries ao datagrid das tarefas

 dataGridEntries.SetDataBinding(dataSetTimesheet,

   "Client.Client2Projects.Project2TimeEntries");

 

A definição da propriedade AllowNavigation como False para cada DataGrid, desativa a navegação mostrada nas Figuras 4 e 5. A definição da propriedade ReadOnly dos DataGrids de clientes e projeto como True garante que as linhas exibidas nesses DataGrids não poderão ser editadas. Consulte essas alterações na Figura 6.

 

image006.gif

Figura 6 Três DataGrids

 

Observe que o controle DataGrid do projeto exibe somente as linhas filhas da linha de cliente selecionada no DataGrid de cliente. Da mesma forma, as linhas de entrada de horários exibidas no DataGrid da entrada de horários são aquelas que são filhas da linha selecionada no DataGrid projeto. Quando outro cliente ou projeto for selecionado, os controles dos DataGrids de projeto e horário serão atualizados automaticamente, para refletir as linhas filhas correspondentes. No controle DataGrid de entrada de tempo, os usuários poderão excluir uma linha selecionando-a e pressionando a tecla Delete, e poderão inserir uma linha inserindo valores na última linha, marcada por um asterisco.

 

Customizar o DataGrid com propriedades

O aplicativo de quadro de horários está agora com seu funcionamento completo — os usuários podem criar entradas e persistir as alterações no banco de dados por meio do objeto SqlDataAdapter das entradas de horários. Mas a UI precisa de cuidados.

Diversas propriedades do DataGrid permitem que você personalize a aparência do DataGrid. Essas propriedades estão relacionadas na Tabela 1. Dentro do Visual Studio .NET é possível escolher entre diversos esquemas pré formatados na caixa de diálogo Auto Format—da mesma forma que você pode aplicar a formatação automática das células em uma planilha do Microsoft® Excel. Essa caixa de diálogo é acessada por meio de um link na parte inferior da janela de propriedades do DataGrid. Sou adepto do esquema "Professional 3", por isso o escolhi aqui. A seleção desse esquema define as propriedades relacionadas na Tabela 1.

 

Tabela 1 Propriedades do DataGrid que afetam a aparência

Propriedade

Descrição

AllowSorting

Indica se o DataGird pode ser reclassificado com um clique no cabeçalho da coluna

AlternatingBackColor

Gera linhas com cores alternadas para criar uma aparência mais legível

BackColor

Cor de fundo do DataGrid

BackgroundColor

Cor da área do DataGrid fora das linhas

BorderStyle

Estilo da borda do DataGrid

CaptionBackColor

Cor de fundo da área de legendas (caption)

CaptionFont

Fonte da legenda (caption) do DataGrid

CaptionForeColor

Cor de primeiro plano da área de legendas (caption)

CaptionText

Texto da legenda (caption) da janela do DataGrid

CaptionVisible

Indica se a legenda (caption) do DataGrid fica visível

ColumnHeadersVisible

Indica se os cabeçalhos das colunas de uma tabela ficam visíveis

FirstVisibleColumn

Índice da primeira coluna visível do DataGrid

FlatMode

Valor que indica se do DataGrid é exibido no modo plano

GridLineColor

Cor das linhas do DataGrid

GridLineStyle

Estilo da linha do DataGrid

HeaderBackColor

Cor de fundo de todos os cabeçalhos de linhas e colunas

HeaderFont

Fonte usada nos cabeçalhos das colunas

HeaderForeColor

Cor de primeiro plano dos cabeçalhos

LinkColor

Cor do texto que pode ser clicado para navegar até uma tabela-filha

LinkHoverColor

Cor a ser assumida por um link quando o ponteiro do mouse passar sobre ele

ParentRowsBackColor

Cor de fundo das linhas-pai

ParentRowsForeColor

Cor de primeiro plano das linhas-pai

ParentRowsLabelStyle

Estilo de exibição dos labels de linhas-pai

ParentRowsVisible

Indica se as linhas-pai de uma tabela ficam visíveis

PreferredColumnWidth

Largura padrão das colunas do DataGrid, em pixels

PreferredRowHeight

Altura preferida da linha para o controle SystemWindowsFormsDataGrid

RowHeadersVisible

Define se os cabeçalhos das linhas ficam visíveis

RowHeaderWidth

Largura dos cabeçalhos das linhas

SelectionBackColor

Cor de fundo das linhas selecionadas

SelectionForeColor

Cor de primeiro plano das linhas selecionadas

 

DataGridTableStyle e DataGridColumnStyle

Em seguida desejo fazer com que algumas tabelas e colunas sejam somente para leitura, ocultar algumas colunas, apresentar colunas numéricas em um determinado formato, identificar as colunas com outros textos e escolher larguras padrão adequadas para as colunas. Essas alterações exigem o uso de objetos DataGridTableStyles e DataGridColumnStyles.

Com os objetos DataGridTableStyle, você pode definir as propriedades de aparência e omitir ou reordenar as colunas exibidas, de maneira seletiva. Acrescentando vários objetos DataGridTableStyle ao TableStyle Collection de um DataGrid, você pode criar diversas aparências diferentes. Isso torna possível alternar programaticamente entre esquemas de exibição. Além disso, quando um único controle DataGrid é estruturado para apresentar diversas tabelas, você pode associar diferentes DataGridTableStyle a cada tabela.

Com os objetos DataGridColumnStyle, você pode afetar a aparência, o comportamento e a formatação dos dados da coluna. DataGridColumnStyle é uma classe abstrata a qual o Microsoft .NET Framework oferece duas implementações: a classe DataGridTextBoxColumn e a DataGridBoolColumn. Se você vincular a um DataGrid sem fornecer objetos DataGridColumnStyle, o DataGrid os criará automaticamente. O tipo criado depende do tipo de dados da coluna associada. Por exemplo, no aplicativo do quadro de horários, a coluna Approved é um System.Boolean. O DataGrid cria um DataGridBoolColumn para exibir essa coluna (observe a caixa de seleção na Figura 6). Todas as demais colunas do aplicativo são exibidas com objetos DataGridTextBoxColumn.

Em seguida, use objetos DataGridTableStyle e DataGridColumnStyle para refinar a aparência do quadro de horários:

Ø       Omitindo a exibição das colunas identity (ID) do banco de dados, ClientID, ProjectID e TimeEntryID nos controles do DataGrid. Como essas colunas são identificadores, com significado somente para o banco de dados, está tudo certo;

Ø       Ajustando as larguras das colunas em cada DataGrid;

Ø       Omitindo a exibição da coluna EmployeeID no controle DataGrid das entradas de horários. Não é necessário exibir a EmployeeID porque todas as entradas de horários sempre pertencerão ao mesmo funcionário. O aplicativo definirá a propriedade DefaultValue da DataColumn relacionada à ID de funcionário do usuário corrente, dando às novas linhas um valor para essa coluna;

Ø       Formatando a coluna Duration no controle DataGrid das entradas de horários, de modo que os valores das colunas sejam exibidos com dois dígitos após o ponto decimal;

Ø       Fazendo com que a coluna Approved no controle DataGrid das entradas de horários seja somente de leitura.

 

Você pode usar a janela de propriedades associada ao controle DataGrid para criar objetos DataGridTableStyle e DataGridColumnStyle. A Figura 7 mostra a caixa de diálogo DataGridTableStyle Collection Editor, acessada com um clique na propriedade TableStyles na janela de propriedades do DataGrid.

 

image007.gif

Figura 7 Propriedades de DataGridTableStyle

 

Quando você adicionar um objeto DataGridTableStyle, o associará a uma das tabelas vinculadas à fonte de dados do controle DataGrid, definindo a propriedade MappingName com o nome da tabela. A Listagem 2 mostra o código gerado pelo Visual Studio .NET para configurar um objeto DataGridTableStyle e um objeto DataGridColumnStyle no aplicativo de quadro de horários.

 

Listagem 2 DataGridTableStyle e DataGridColumnStyle

//

// dataGridClients

//

•••

dataGridClients.TableStyles.AddRange(new

System.Windows.Forms.DataGridTableStyle[] {

   dataGridTableStyleClients});

•••

//

// dataGridTableStyleClients

//

dataGridTableStyleClients.DataGrid = dataGridClients;

dataGridTableStyleClients.GridColumnStyles.AddRange(new

    System.Windows.Forms.DataGridColumnStyle[] {

    dataGridTextBoxColumnClients});

dataGridTableStyleClients.HeaderForeColor =

    System.Drawing.SystemColors.ControlText;

dataGridTableStyleClients.MappingName = "Client";

//

// dataGridTextBoxColumnClients

//

dataGridTextBoxColumnClients.Format = "";

dataGridTextBoxColumnClients.FormatInfo = null;

dataGridTextBoxColumnClients.HeaderText = "Clients";

dataGridTextBoxColumnClients.MappingName = "ClientName";

dataGridTextBoxColumnClients.Width = 200;

 

Adicionando um objeto DataGridTextBoxColumn ou DataGridBoolColumn ao GridColumnStyle collection do objeto DataGridTableStyle, você indica quais colunas deseja exibir dentro do estilo da tabela. Para associar um determinado objeto DataGridColumnStyle a uma coluna da fonte de dados, você define sua propriedade MappingName como nome da coluna. Outras propriedades importantes de DataGridColumnStyle são HeaderText, Width, ReadOnly e Format. A propriedade HeaderText controla o texto a ser exibido no cabeçalho da coluna; o padrão é o nome da coluna. Você pode definir a propriedade Format de objetos DataGridTextBoxColumn com um formato específico do .NET Framework para exibir o texto em um determinado formato. Por exemplo, definir a propriedade Format do objeto DataGridTextBoxColumn da coluna Duration da tabela TimeEntry com "#.00", faz com que o valor seja exibido com dois dígitos após o ponto decimal.

Contudo, há um problema — os esquemas de exibição selecionados com o AutoFormat são perdidos. Eles são aplicados somente ao próprio DataGrid, e não a quaisquer objetos DataGridTableStyle associados. Como é prático usar o AutoFormat para desenhar a tela e objetos DataGridTableStyle para customizar a exibição de colunas, adicionei o método da Listagem 3 para copiar as propriedades DataGrid padrão para um objeto DataGridTableStyle.

 

Listagem 3 Método CopyDefaultTableStyle

 

// Copia as propriedades relacionadas a

// exibição do DataGrid para o DataGridTableStyle

private void CopyDefaultTableStyle(DataGrid datagrid,

    DataGridTableStyle ts)

{

    ts.AllowSorting = datagrid.AllowSorting;

    ts.AlternatingBackColor =

    datagrid.AlternatingBackColor;

    ts.BackColor = datagrid.BackColor;

    ts.ColumnHeadersVisible =

    datagrid.ColumnHeadersVisible;

    ts.ForeColor = datagrid.ForeColor;

    ts.GridLineColor = datagrid.GridLineColor;

    ts.GridLineStyle = datagrid.GridLineStyle;

    ts.HeaderBackColor = datagrid.HeaderBackColor;

    ts.HeaderFont = datagrid.HeaderFont;

    ts.HeaderForeColor = datagrid.HeaderForeColor;

    ts.LinkColor = datagrid.LinkColor;

    ts.PreferredColumnWidth =

    datagrid.PreferredColumnWidth;

    ts.PreferredRowHeight =

    datagrid.PreferredRowHeight;

    ts.ReadOnly = datagrid.ReadOnly;

    ts.RowHeadersVisible =

    datagrid.RowHeadersVisible;

    ts.RowHeaderWidth = datagrid.RowHeaderWidth;

    ts.SelectionBackColor =

    datagrid.SelectionBackColor;

    ts.SelectionForeColor =

    datagrid.SelectionForeColor;

}

 

Para copiar as propriedades relacionadas à exibição de um controle DataGrid para um objeto DataGridTableStyle que será usado pelo DataGrid, você pode usar o método CopyDefaultTableStyle. O código a seguir mostra como copiar os parâmetros padrão do grid para os estilos de tabela associados:

 

// Copia os parâmetros padrão do Grid de dados

// para os estilos de tabela associados

CopyDefaultTableStyle(dataGridClients, dataGridTableStyleClients);

CopyDefaultTableStyle(dataGridProjects, dataGridTableStyleProjects);

CopyDefaultTableStyle(dataGridEntries, dataGridTableStyleEntries);

 

A Figura 8 mostra o resultado da aplicação dos estilos de tabela e coluna.

 

image009.jpg

Figura 8 Resultado dos estilos

 

Implementação de um estilo de colunas personalizado

A aplicação de estilos de tabela e coluna melhorou a exibição dos dados, mas antes de chamá-la um dia, observe a coluna Activity na Figura 8. Volte a observar na Figura 2 que a coluna ActivityID da tabela TimeEntry é uma chave estrangeira na tabela Activity. Utilizar a descrição da atividade associada seria mais útil do que exibir esse identificador (ID), mas as Stored Procedures que inserem no banco de dados e o atualizam precisam do identificador.

Uma solução comum é exibir descrições válidas em um controle combobox. Derivando-se uma classe de estilo de coluna a partir do DataGridColumnStyle abstrato, é possível criar um  estilo de coluna que utiliza um controle combobox, para permitir a seleção de valores da colunas. A última seção deste artigo implementa uma classe de estilo de coluna de uma combobox personalizada. Contudo, para entendê-la será preciso ter um conhecimento básico de vinculação de dados (data binding) do Windows Forms.

Um resumo sobre a vinculação de dados (data binding) do Windows Forms

Os conceitos que você deve conhecer incluem o funcionamento da vinculação de dados, tanto simples como complexa, como o Windows Forms trata a sincronia entre vários controles vinculados à mesma fonte de dados e como são usados os contextos de vinculação.

Primeiro, os objetos de vinculação são usados para vincular uma propriedade do controle a um único elemento de dados. Um objeto de vinculação descreve uma associação entre uma propriedade do controle e uma fonte de dados. Você pode, por exemplo, criar um objeto de vinculação (Binding) para vincular a propriedade Text de um controle textbox ao valor de uma determinada coluna de uma DataTable. A vinculação simples refere-se à capacidade de um controle de ligar a um único elemento de dados.

Todos os controles do Windows Forms expõem uma propriedade DataBindings que representa uma coleção de objetos Binding. Normalmente um aplicativo vincula uma fonte de dados à propriedade do controle que exibe alguns dados ao usuário, como a propriedade Text de uma textbox. Contudo, a classe Binding permite que você vincule qualquer propriedade de controle a um membro da fonte de dados. Por exemplo, este código adiciona objetos Binding a coleção DataBindings de dois controles de textbox:

 

textBox1.DataBindings.Add("Text", userList, "Name");

textBox1.DataBindings.Add("BackColor", userList, "FavoriteColor");

textBox2.DataBindings.Add("Text", userList, "PhoneNumber");

 

Neste exemplo, userList é uma instância de ArrayList preenchida com objetos do tipo User. O tipo User expõe as propriedades Name, FavoriteColor e PhoneNumber. A primeira instrução vincula a propriedade Text de um controle textbox à propriedade Name de um objeto em um ArrayList chamado userList. A segunda instrução vincula a propriedade BackColor do mesmo controle textbox à propriedade FavoriteColor. A última instrução vincula a propriedade Text de um segundo controle textbox à propriedade PhoneNumber.

Segundo, as propriedades DataSource e DataMember dos controles DataGrid, combobox, listbox, e ErrorProvider são usadas para suportar a vinculação de dados. Controles que suportam vinculação complexa de dados têm a capacidade de vincular e, ao mesmo tempo, exibir um conjunto de itens, como todas as linhas de uma DataTable. Defina a propriedade DataSource com uma fonte de dados como DataTable ou um ArrayList. Quando o objeto referenciado pela DataSource for de um tipo que puder conter várias listas, especialmente o DataSet, você poderá definir a propriedade DataMember para indicar que lista será o destino da vinculação.

O controle DataGrid possui um método SetDataBinding que define as propriedades DataSource e DataMember. O aplicativo de quadro de horários usa este método:

 

// Vincula a tabela Clients ao datagrid de clientes

dataGridClients.SetDataBinding(dataSetTimesheet, "Clients");

 

Terceiro, os objetos de gerenciamento de vinculação gerenciam a sincronia entre os diversos controles ligados à mesma fonte de dados. Normalmente uma fonte de dados é uma instância de um tipo ADO.NET, como o DataSet, DataTable, DataView ou DataViewManager, ou outro tipo como ArrayList, que implementa a interface IList. Nenhum desses tipos implementa mecanismos internos para denotar um item na lista como corrente. Em aplicativos que usam vinculação de dados, a ausência desse mecanismo interno apresenta um problema: como vários controles vinculados à mesma fonte de dados conseguem permanecer sincronizados, de modo que todos os controles Windows Forms ligados exibam um valor da mesma linha ou item? No exemplo anterior, como o aplicativo pode ter certeza de que o nome (Name) e o telefone (PhoneNumber) exibidos nos controles textbox pertencerão sempre ao mesmo usuário do ArrayList?

A vinculação de dados do Windows Forms resolve esse problema com dois tipos derivados da classe abstrata BindingManagerBase—a classe CurrencyManager e a PropertyManager. Na primeira vez que qualquer controle se vincula a uma determinada fonte de dados, é criado um objeto BindingManagerBase automaticamente. Esse objeto é compartilhado por controles que, em seguida, se vinculam à mesma fonte de dados.

São usados objetos CurrencyManager para definir a posição atual em fontes de dados baseadas em listas (list-based data sources).  Por exemplo, o código a seguir define a propriedade Position do CurrencyManager associado à userList dada no primeiro ponto, para definir o item seguinte como corrente:

 

myCurrencyManager = (CurrencyManager)BindingContext[userList];

myCurrencyManager.Position += 1;

 

Isso faz com que as propriedades do controle textbox vinculada alterem os valores da propriedade dados pelo item User seguinte de userList. Explicarei em seguida o objeto BindingContext.

Normalmente os controles se vinculam a tipos baseados em listas, como as fontes de dados que mencionei há pouco. Contudo, qualquer classe que exponha propriedades públicas pode ser vinculada a dados. Você poderia, por exemplo, vincular a propriedade Text de um controle textbox a uma única instância da classe User descrita há pouco, em vez de ligar a um ArrayList de itens de User. Os objetos PropertyManager são usados para gerenciar as fontes de dados que não são baseadas em listas. Os objetos PropertyManager têm uma propriedade Position, mas sua definição não tem qualquer efeito. Finalmente, os objetos CurrencyManager e PropertyManager existem dentro de um BindingContext. Um BindingContext é apenas uma coleção de objetos CurrencyManager e PropertyManager. Pode ser criado mais de um BindingContext para um único formulário mas, por padrão, é criado um único BindingContext automaticamente para cada Windows Form. Cada controle somente pode ser associado a um único BindingContext.

Cada controle do Windows possui uma propriedade BindingContext que retorna o BindingContext a que o controle pertence. Você pode usar essa propriedade para obter o CurrencyManager ou PropertyManager relacionado com a fonte de dados vinculada ao controle. O código a seguir mostra como obter o CurrencyManager associado à fonte de dados do controle:

 

CurrencyManager cm = (CurrencyManager)(BindingContext[DataSource]);

 

Por meio da propriedade List do CurrencyManager, será possível então acessar a fonte de dados vinculada, neste caso um objeto DataView:

 

DataView dataview = ((DataView)cm.List);

 

A possibilidade de acessar a fonte de dados por meio de uma propriedade BindingContext do controle pode ser útil. Você verá como a implementação da DataGridComboBoxColumn usa essa técnica para acessar a fonte de dados vinculada ao membro do controle da combobox.

A classe DataGridComboBoxColumn

Lembre-se que o aplicativo de quadro de horário precisa de um estilo de coluna (column style) de caixa de combinação com as seguintes características:

Ø       Quando a célula do DataGrid é editada, exibe um controle combobox dentro dos limites da célula. Quando não está sendo editada, a célula exibe a descrição selecionada;

Ø       Os aplicativos que usam a classe de estilo de coluna (column style) podem ligar um membro da classe combobox a qualquer objeto do DataTable. Definindo-se as propriedades DisplayMember e ValueMember da combobox, o aplicativo pode especificar que coluna do DataTable contém as descrições a serem exibidas e que coluna contém um identificador (ID) correspondente para retornar ao controle DataGrid;

Ø       Como se trata de uma necessidade comum em aplicativos com DataGrid, o estilo de coluna (column style) produzido dever ser genérico e reutilizável.

 

Esta seção apresenta uma visão geral de uma implementação de estilo de coluna (column style) de uma combobox. A Listagem 4 mostra a implementação completa. A classe deriva da classe DataGridTextBoxColumn, em vez da classe DataGridColumnStyle. Isso permite que a classe de estilo de coluna (column style) pegue emprestadas funcionalidades de DataGridTextBoxColumn, como a possibilidade de exibir texto com facilidade quando a célula não estiver sendo editada.

 

Listagem 4 Classe DataGridComboBoxColumn

// Deriva classe do DataGridTextBoxColumn

public class DataGridComboBoxColumn : DataGridTextBoxColumn

{

  // Controle combobox

  private ComboBox comboBox;

  private CurrencyManager cm;

  private int iCurrentRow;

   

  // Construtor do combobox criado, registra

  // o tratamento de evento de mudança de

  // seleção, registra o tratamento do evento

  // de perca do foco

  public DataGridComboBoxColumn()

  {

    this.cm = null;

    // Cria combobox e aplica o estilo DropDownList

    this.comboBox = new ComboBox();

    this.comboBox.DropDownStyle =

    ComboBoxStyle.DropDownList;

       

    // Adiciona o tratamento do evento quando

    // a combobox perder o foco

    this.comboBox.Leave += new EventHandler(comboBox_Leave);

  }

   

  // Propriedade para suportar acesso a combobox

  public ComboBox ComboBox

  {

      get { return comboBox; }

  }      

           

  // Na edição, adiciona o tratamento de evento

  // Scroll e exibe a combobox

  protected override void Edit(System.Windows.Forms.CurrencyManager

       source, int rowNum, System.Drawing.Rectangle bounds, bool readOnly,

       string instantText, bool cellIsVisible)

  {

    base.Edit(source, rowNum, bounds,

    readOnly, instantText, cellIsVisible);

 

    if (!readOnly && cellIsVisible)

    {

       //Salva a linha atual no DataGrid e

       //usa o gerenciador associado a fonte de

       //dados para o DataGrid

       this.iCurrentRow = rowNum;

       this.cm = source;

   

       //Adiciona o tratamento de evento

       //Scroll para notificação do DataGrid

       this.DataGridTableStyle.DataGrid.Scroll

         += new EventHandler(DataGrid_Scroll);

 

       // Localiza a combobox com a célula atual

       this.comboBox.Parent = this.TextBox.Parent;

       Rectangle rect =  this.DataGridTableStyle.DataGrid.GetCurrentCellBounds();

       this.comboBox.Location = rect.Location;

       this.comboBox.Size =

          new Size(this.TextBox.Size.Width,

                   this.comboBox.Size.Height);

 

       // Define a combobox para exibir o texto

       this.comboBox.SelectedIndex = this.comboBox.FindStringExact(this.TextBox.Text);

 

       // Torna a combobox visível e posiciona

       // o texto no topo do controle

       this.comboBox.Show();

       this.comboBox.BringToFront();

       this.comboBox.Focus();

    }

  }

 

  // Define a linha e captura o valor associado

  // a mesma. Usa o valor para pesquisar o item

  // exibido associado a fonte de dados

  protected override object

    GetColumnValueAtRow(System.Windows.Forms.CurrencyManager source, int rowNum)

  {

    // Define o número da linha no DataGrid

    object obj =  base.GetColumnValueAtRow(source, rowNum);

       

    // Repete através da fonte de dados para

    // vincular o ColumnComboBox

    CurrencyManager cm = (CurrencyManager)

        (this.DataGridTableStyle.DataGrid.BindingContext[this.comboBox.DataSource]);

    // Assume que o DataGrid está vinculado ao

    // DataView ou DataTable

    DataView dataview = ((DataView)cm.List);

    int i;

    for (i = 0; i < dataview.Count; i++)

    {

      if (obj.Equals(dataview[i][this.comboBox.ValueMember]))

        break;

    }

      

    if (i < dataview.Count)

     return dataview[i][this.comboBox.DisplayMember];

    

     return DBNull.Value;

  }

 

  // Define a linha e uma exibição, vincula a

  // fonte de dados e pesquisa o valor associado

  protected override void

        SetColumnValueAtRow(System.Windows.Forms.CurrencyManager source, int rowNum, object value)

  {

    object s = value;

    // Repete através da fonte de dados o

    // vínculo para o ColumnComboBox

    CurrencyManager cm = (CurrencyManager)

        (this.DataGridTableStyle.DataGrid.BindingContext[this.comboBox.DataSource]);

    // Vincula o DataGrid associado ao

    // DataView ou DataTable

    DataView dataview = ((DataView)cm.List);

    int i;

 

    for (i = 0; i < dataview.Count; i++)

    {

      if (s.Equals(dataview[i][this.comboBox.DisplayMember]))

         break;

    }

 

    // Se o item retornado foi encontrado,

    // retorna o valor correspondente,

    // caso contrário retorna DbNull.Value

    if(i < dataview.Count)

       s =  dataview[i][this.comboBox.ValueMember];

    else

       s = DBNull.Value;

    base.SetColumnValueAtRow(source, rowNum, s);

  }

 

  // No DataGrid Scroll, oculta a combobox

  private void DataGrid_Scroll(object sender, EventArgs e)

  {

    this.comboBox.Hide();

  }

 

  // Ao perder foco da combobox, define o valor

  // da coluna, oculta a combobox,

  // e abandona o tratamento do evento scroll

  private void comboBox_Leave(object sender, EventArgs e)

  {

    DataRowView rowView = (DataRowView) this.comboBox.SelectedItem;

    string s = (string) rowView.Row[this.comboBox.DisplayMember];

 

    SetColumnValueAtRow(cm, iCurrentRow, s);

    Invalidate();

 

    this.comboBox.Hide();

    this.DataGridTableStyle.DataGrid.Scroll -=

        new EventHandler(DataGrid_Scroll);           

  }

}

 

A classe DataGridComboBoxColumnStyle contém um membro privado da combobox e uma propriedade read-only para acessá-lo. O construtor de classe também registra um manipulador de eventos para receber notificação quando o foco sair da combobox.

Sobrescrevendo o método Edit, a classe pode detectar o início da edição salvando a linha atual e o objeto CurrencyManager. O código então registra um manipulador de eventos para o evento DataGrid Scroll. Finalmente, esse método exibe o controle combobox dentro dos limites da célula DataGrid, selecionando o valor de exibição associado ao valor da célula.

O manipulador de eventos Leave detecta o fim da edição. Esse método chama o método SetColumnValueAtRow para salvar o valor associado ao item selecionado atualmente no controle combobox. Para isso, usa o objeto CurrencyManager e a linha salva no método Edit. Em seguida, esse método oculta o controle combobox e desfaz o registro do manipulador de eventos Scroll.

Os métodos GetColumnValueAtRow e SetColumnValueAtRow da classe básica obtêm e salvam um valor em uma célula DataGrid. Sobrescrevendo o método GetColumnValueAtRow, o código de DataGridComboBoxColumnStyle pode traduzir um valor de exibição para um valor correspondente para salvar. Quando usado pelo aplicativo de quadro de horários, esse método trocará a descrição da atividade selecionada (Activity) por um valor de ActivityID. Para isso, o método sobrescrito chama o método da classe básica para obter o valor de célula do DataGrid. Ele usa a propriedade BindingContext do DataGrid para obter o objeto DataView associado à fonte de dados vinculada. O objeto DataView é usado para pesquisar e retornar a coluna de descrição (DisplayMember) associada ao valor da célula.

A sobrescrita do método SetColumnValueAtRow faz o oposto — um valor da célula salvo no DataGrid é traduzido para um valor a ser exibido. Um objeto DataView, obtido com a propriedade BindingContext do DataGrid, é usado para localizar o valor da coluna (Value Member) associada ao item de exibição. A classe base SetColumnValueAtRow é então chamada para definir o valor da célula do DataGrid com o item do valor encontrado. Finalmente, o manipulador de evento Scroll oculta a combobox quando o DataGrid é rolado.

O estilo de coluna (column style) DataGridComboBoxColumn

Para usar a combobox no DataGrid de entrada de horários, são necessárias três alterações no formulário existente. Primeiro, todas as referências ao objeto DataGridTextBoxColumn existentes para a coluna ActivityID são substituídas por uma referência ao objeto DataGridComboBoxColumn no método InitializeComponent do formulário.

Segundo, o seguinte código é adicionado ao evento Load do formulário para vincular a combobox à fonte de dados da tabela Activity:

 

// Vincula as atividades a comboBoxActivities

dataGridComboBoxColumnActivity.ComboBox.DataSource =

    dataSetTimesheet.Tables["Activity"];

dataGridComboBoxColumnActivity.ComboBox.DisplayMember = "Description";

dataGridComboBoxColumnActivity.ComboBox.ValueMember = "ActivityID";

 

A segunda e terceira instruções instruem a combobox para exibir a coluna Description, usando o valor na coluna ActivityID correspondente.

Finalmente, a definição da altura da linha do controle DataGrid com a altura da combobox mais o espaço para uma pequena margem, garante que a combobox será totalmente exibida dentro das células do controle DataGrid.

 

// Define a altura da combobox

dataGridEntries.PreferredRowHeight =

   dataGridComboBoxColumnActivity.ComboBox.Height + 1;

 

Conclusão

A navegação embutida, mais a possibilidade de vincular ao DataSet e customizar todos os aspectos de sua aparência, fazem do DataGrid uma ferramenta obrigatória para aplicativos Windows Forms baseados em dados. E a arquitetura expansível dos estilos de colunas (column styles) os coloca entre os recursos mais poderosos do DataGrid. Este artigo descreveu um exemplo simples de um estilo de coluna (column style) customizado. Ampliando essa idéia para implementar seus próprios estilos de coluna, você poderá permitir que os usuários consultem e editem dados dentro do DataGrid de qualquer maneira que você achar apropriada e intuitiva para resolver seus problemas de exibição e acesso de dados.