Três modos de montar um drop down list em um DBGri

A seguir, mostraremos como colocar um drop down list em um DBGrid. Crie interfaces de usuário visualmente mais atraentes para editar campos lookup em um DBGrid - usando a propriedade PickList de uma coluna de DBGrid.

Este é o segundo artigo da série de artigos chamada "Adding components to a DBGrid" ("Acrescentando componentes a um DBGrid"). A idéia aqui, é mostrar como colocar quase qualquer controle Delphi (componente visual), em uma célula de um DGBrid. Se você não estiver familiarizado com a idéia, por favor, leia antes o artigo acima mencionado.

Colocar um drop down list em um DBGrid?

Como vimos no artigo anterior, há muitos modos (e razões) para personalizarmos a saída de um DBGrid: suponha você tem um campo lookup em um DataSet. Um modo de explicar um campo lookup de banco de dados é dizer que campos lookup são campos que podem aceitar *somente* valores de uma lista fixa de valores ("static" ou de um DataSet). Vejamos um exemplo: se você tem uma tabela Products com um campo que contém o nome do provedor, quando for editar este campo, seria bom permitir ao usuário a escolha do nome do provedor de uma lista de provedores (contida na tabela Providers).

Criando uma aplicação exemplo

Para começar, inicie o Delphi e, no formulário padrão vazio, coloque um TDBGrid, um TADOTable, um TADOConnection e um TDataSource. Deixe todos os nomes de componente como o Delphi os chamou quando colocados no formulário (DBGrid1, ADOQuery1, AdoTable1,...). Use o Object Inspector para configurar a propriedade ConnectionString do componente ADOConnection1 (TADOConnection), para apontar para o banco de dados de exemplo QuickiesContest.mdb do MS Access. Conecte o DBGrid1 ao DataSource1, o DataSource1 ao AdoTable1 e finalmente o AdoTable1 ao ADOConnection1. A propriedade TableName de AdoTable1, deverá apontar para a tabela Articles (fazendo com que o DBGrid exiba os registros da tabela Articles).

Nota: Nada o impede de usar os qualquer outro componente DataSet do Delphi, tais como TTable, TQuery,...

Se você configurou todas as propriedades corretamente, quando rodar a aplicação (desde que a propriedade Active do componente AdoTable1 seja True), você deverá obter a saída.

Note que o campo Subject pode aceitar só valores da tabela Subjects (definida via integridade referencial). O que você precisa “observar" na figura anterior é que, ao editar esta campo sem uma lista, se tentássemos substituir um valor com outro valor não encontrado na tabela Subjects (tabela “master”), receberíamos a mensagem de erro "You cannot add or change a record because a related record is required in table Subjects" (Você não pode adicionar ou alterar um registro, porque um registro relacionado é requerido na tabela Subjects”).

Existem três modos de configurar um campo lookup em Delphi

Se você tem um formulário de dados onde todas as edições são realizadas em um DBGrid, o melhor é criar um novo campo (lookup) para o DataSet.

Se um formulário é hospedeiro de um conjunto de controles de dados (DBEdit, DBComboBox, etc.), faz sentido usar apenas o DBLookupComboBox sem criar um campo novo.

O último modo, é usar colunas de um DBGrid com sua propriedade PickList, novamente sem criar um campo novo. Esta abordagem não representa um verdadeiro lookup, mas você verá que pode agir como um. A PickList exibe a lista de valores que o usuário pode selecionar para o valor do campo – tal qual um DBComboBox padrão - mas dentro de um DBGrid.

Analisaremos cada um dos modos, começando pelo último

Mais adiante, você descobrirá como fazer a exibição de célula de uma coluna DBGrid, com um drop-down list de valores.

A seguir, veremos como colocar uma drop-down list em um DBGrid. Crie interfaces do usuário visualmente mais atraentes para editar campos lookup dentro de um DBGrid - usando a propriedade PickList de uma coluna de DBGrid.

Agora, que você sabe o que é um campo lookup e quais são as opções para exibir um campo lookup no DBGrid do Delphi, está na hora de ver como usar a propriedade PickList de uma coluna de DGBrid, para permitir a um usuário a escolha de um valor para um campo lookup de uma drop-down list.

Uma informação rápida sobre a propriedade Columns do DBGrid

Um controle DBGrid tem uma propriedade Columns - uma coleção de objetos TColumn que representam todas as colunas em um controle grid. As colunas podem ser configuradas em tempo de projeto via o Columns editor ou programaticamente em tempo de execução. Você normalmente adicionará Columns a um DBGrid, quando quiser definir a aparência de uma coluna, como os dados na coluna serão exibidos e para acessar as propriedades, eventos e métodos de TDBGridColumns em tempo de execução.

Um grid personalizado permite configurar colunas múltiplas para apresentar visões diferentes do mesmo DataSet (diferentes ordens de colunas, escolhas de campo diferentes e diferentes cores e fontes de colunas, por exemplo). Cada Column em um grid é “vinculado" a um campo em um DataSet exibido no grid. Ainda mais, cada coluna tem uma propriedade PickList. A propriedade PickList, lista os valores que o usuário pode selecionar para o valor do campo vinculado à coluna.

Preenchendo o PickList

O que você verá aqui é como preencher a String List com valores de outro DataSet em tempo de execução. Lembre-se que estamos editando a tabela Articles - e que um campo Subject pode aceitar só valores da tabela Subjects: situação ideal para o PickList!

A seguir, mostramos como configurar a propriedade PickList. Primeiro, adicionamos uma chamada ao procedimento SetupGridPickList no manipulador de evento OnCreate do Formulário.


procedure TForm1.FormCreate(Sender: TObject);

begin

  SetupGridPickList('Subject', 'SELECT Name FROM Subjects');

end;

O modo mais fácil de criar a procedure SetupGridPickList, é adicioná-la na declaração da parte privada do formulário, e teclar a combinação CTRL + SHIF + C. O code completion do Delphi fará o resto:


...

type

  TForm1 = class(TForm)

...

  private

    procedure SetupGridPickList(

        const FieldName : string;

        const sql : string);

  public

...
Nota: a procedure SetupGridPickList tem dois parâmetros. O primeiro parâmetro, FieldName, é o nome do campo que queremos que se comporte como um campo lookup; o segundo parâmetro, sql, é a expressão SQL que usaremos para povoar o PickList com os valores possíveis - em geral a expressão SQL deveria devolver um datataset com um só campo.

Vejamos o aspecto do SetupGridPickList:


procedure TForm1.SetupGridPickList(const FieldName, sql: string);

var

  slPickList:TStringList;

  Query : TADOQuery;

  i : integer;

begin

  slPickList:=TStringList.Create;

  Query := TADOQuery.Create(self);

  try

    Query.Connection := ADOConnection1;

    Query.SQL.Text := sql;

    Query.Open;

    //Preencher o string list

    while not Query.EOF do

    begin

      slPickList.Add(Query.Fields[0].AsString);

      Query.Next;

    end; //while

 

    //colocar a lista na coluna correta

    for i:=0 to DBGrid1.Columns.Count-1 do

      if DBGrid1.Columns[i].FieldName = FieldName then

      begin

        DBGrid1.Columns[i].PickList:=slPickList;

        Break;

      end;

  finally

    slPickList.Free;

    Query.Free;

  end;

end; (*SetupGridPickList*)

Isso é tudo. Agora, quando você clica na coluna Subject (para entrar no modo de edição).

Nota 1: por padrão, o drop-down list exibe 7 valores. Você pode mudar o tamanho desta lista configurando a propriedade DropDownRows.
Nota 2: nada o impede de povoar o PickList de uma lista de valores que não provenha de uma tabela de banco de dados. Por exemplo, se você tem um campo que só aceita nomes de dias da semana ('segunda-feira',..., 'Domingo'), poderá construir um "PickList hard-coded".
Ih, preciso clicar o PickList 4 vezes...

Note que quando você quiser editar o campo que exibe um drop-down list, precisará clicar 4 vezes na célula para de fato escolher um valor. O próximo trecho de código, adicionado ao manipulador de evento OnCellClick do DBGrid, simula o pressionar das teclas F2, seguido por Alt + DownArrow.


procedure TForm1.DBGrid1CellClick(Column: TColumn);

begin

  //Fazer o drop-down pick list apareça masi rapidamente

  if Column.PickList.Count > 0 then

  begin

    keybd_event(VK_F2,0,0,0);

    keybd_event(VK_F2,0,KEYEVENTF_KEYUP,0);

    keybd_event(VK_MENU,0,0,0);

    keybd_event(VK_DOWN,0,0,0);

    keybd_event(VK_DOWN,0,KEYEVENTF_KEYUP,0);

    keybd_event(VK_MENU,0,KEYEVENTF_KEYUP,0);

  end;

end;

A idéia aqui, é mostrar como colocar quase qualquer controle Delphi (componente visual) em uma célula de um DGBrid.

Vimos campos lookup e quais são as opções de exibição de um campo lookup em um DBGrid. Está na hora de ver como colocar um DBLookupComboBox em uma célula de um DGBrid, para permitir ao usuário a escolha de um valor para um campo lookup de um drop-down list.

A seguir, mostraremos como colocar um DBLookupComboBox em um DBGrid. Crie interfaces do usuário visualmente mais atraentes para editar campos lookup dentro de um DBGrid – coloque um DBLookupComboBox em uma célula de um DBGrid.

DBLookupComboBox em um DBGrid?

Sim, da mesma maneira que podemos colocar um check box em uma célula de um DBGrid (para editar campos boolean), podemos usar a mesma técnica para pôr um combobox em uma célula que contém um campo lookup.

Lembremos que um DBLookupComboBox é usado em aplicações banco de dados, para apresentar ao usuário uma lista restrita de escolhas para configurar um valor de campo válido. Quando um usuário selecionar um item da lista, o valor do campo correspondente será alterado no DataSet subjacente. Em geral, você usa um DBLookupComboBox em um formulário de entrada de dados, sem relação com um DBGrid. Agora, faremos com que um DBLookupComboBox flutue por cima de uma célula que contém um campo lookup.

Criando uma aplicação exemplo

Note que já criamos, no artigo anterior, uma aplicação exemplo que contém um DBGrid e vários componentes relacionados a DB. Vejamos como:

Para começar, inicie o Delphi e, no formulário padrão vazio coloque um TDBGrid, um TADOTable, um TADOConnection e um TDataSource. Deixe os nomes dos componentes como o Delphi os chamou quando colocados no formulário (DBGrid1, ADOQuery1, AdoTable1,...).

Use o Object Inspector para configurar a propriedade ConnectionString do componente ADOConnection1 (TADOConnection) para apontar para o banco de dados de exemplo QuickiesContest.mdb do MS Access. Conecte o DBGrid1 ao DataSource1, o DataSource1 ao AdoTable1 e finalmente o AdoTable1 ao ADOConnection1. A propriedade TableName de AdoTable1 deverá apontar para tabela Articles (fazendo com que o DBGrid exiba os registros da mesma). Nota (novamente): nada o impede de usar qualquer outro componente DataSet do Delphi, tal como TTable, TQuery,...

Agora, dê uma olhada no campo AuthorEmail. Note que este campo pode aceitar só valores de campo E-mail da tabela Authors (definido através de integridade referencial). O que você precisa “observar" na figura abaixo é que, ao editar este campo sem lookup, se você tentar substituir um valor com outro valor não encontrado na tabela Authors (tabela "mestre"), receberá a mensagem de erro "You cannot add or change a record because a related record is required in table Authors" (“Você não pode adicionar ou alterar um registro porque um registro relacionado é requerido na tabela Authors”).

Ok. Primeiro precisamos adicionar vários outros componentes em um formulário, junto com um DBLookupComboBox. A seguir temos configurá-los e fazer um pouco de magia Delphi.

Vejamos como colocar um DBLookupComboBox em um DBGrid. Crie interfaces do usuário visualmente mais atraentes para editar campos lookup dentro de um DBGrid – coloque um DBLookupComboBox em uma célula de um DBGrid.

Esta é que é uma grande idéia! Vejamos como criar os melhores editores de dados para um grid!

Criando um lookup com um DBLookupComboBox

Para exibir um DBLookupComboBox dentro de uma célula de um DBGrid, precisaremos tornar um disponível em tempo de execução. Selecione "data controls" na Paleta de Componentes e escolha um DBLookupComboBox. Coloque-o em qualquer lugar no formulário (deixe o nome padrão, "DBLookupComboBox") - não importa onde, pois a maior parte do tempo será invisível ou flutuará por cima do grid.

Em seguida, temos que adicionar mais um DataSource e um componente DataSet para preencher o "combobox” com valores. Coloque um TDataSource (nome: DataSource2) e um TAdoQuery (nome: ADOQuery1) em qualquer lugar do formulário.

Para que o DBLookupComboBox trabalhe corretamente, outras propriedades devem ser configuradas, estas propriedades são fundamentais para a conexão de lookup:

  • DataSource e DataField determinam a conexão principal. O DatField é um campo no qual inserimos o valores lookup.
  • ListSource é a fonte do DataSet de lookup.
  • KeyField identifica o campo no ListSource que tem que casar com o valor do campo DataField.
  • ListFields é(são) o(s) campo(s) (um ou mais) do DataSet de lookup que serão de fato exibidos no combobox. ListField pode mostrar mais de um campo.

Nomes de campo múltiplos deveriam ser separados através de ponto-e-vírgulas. Você tem que configurar um valor grande o bastante para o DropDownWidth (de um ComboBox), para realmente ver colunas de dados múltiplas. Vejamos como configurar todas as propriedades importantes do código (no manipulador de evento OnCreate do formulário):


procedure TForm1.FormCreate(Sender: TObject);

begin

 with DBLookupComboBox1 do

 begin

   DataSource := DataSource1; // -> AdoTable1 -> DBGrid1

   ListSource := DataSource2;

   DataField   := 'AuthorEmail'; // de AdoTable1 – exibido no DBGrid

   KeyField  := 'Email';

   ListFields := 'Name; Email'; 

   Visible    := False;

 end;

 

 DataSource2.DataSet := AdoQuery1;

 AdoQuery1.Connection := AdoConnection1;

 AdoQuery1.SQL.Text := 'SELECT Name, Email FROM Authors';

 AdoQuery1.Open;

end;
Nota: quando você quiser exibir mais de um campo em um DBLookupComboBox, como no exemplo acima, tem que ter certeza que todas as colunas estarão visíveis. Isto é determinado configurando a propriedade DropDownWidth. Porém, você verá que inicialmente temos que configurá-la com um valor muito grande, o que resulta em um lista drop-down muito larga na maioria dos casos.

Uma solução alternativa é configurar o DisplayWidth de um Campo particular mostrado em uma drop-down list. O próximo trecho de código, colocado dentro do evento OnCreate do formulário, garante que o nome de autor e o e-mail é exibido dentro da drop-down list:


AdoQuery1.FieldByName('Email').DisplayWidth:=10;

AdoQuery1.FieldByName('Name').DisplayWidth:=10;

AdoQuery1.DropDownWidth:=150;

O que falta é fazer com que efetivamente o combobox paire por cima de uma célula (quando em modo de edição), exibindo o campo AuthorEmail. Já vimos a teoria - mostraremos aqui apenas o código (você poderá carregar o projeto inteiro depois):

Primeiro, precisamos ter certeza de que o DBLookupComboBox1 está sendo movido (e redimensionado) por cima da célula na qual o campo AuthorEmail é exibido.


procedure TForm1.DBGrid1DrawColumnCell

  (Sender: TObject;

   const Rect: TRect;

   DataCol: Integer;

   Column: TColumn;

   State: TGridDrawState);

begin

  if (gdFocused in State) then

  begin

    if (Column.Field.FieldName = DBLookupComboBox1.DataField) then

    with DBLookupComboBox1 do

    begin

      Left := Rect.Left + DBGrid1.Left + 2;

      Top := Rect.Top + DBGrid1.Top + 2;

      Width := Rect.Right - Rect.Left;

      Width := Rect.Right - Rect.Left;

      Height := Rect.Bottom - Rect.Top;

      Visible := True;

    end;

  end

end;

Portanto, quando sair da célula, temos que esconder o combobox:


procedure TForm1.DBGrid1ColExit(Sender: TObject);

begin

  if DBGrid1.SelectedField.FieldName = DBLookupComboBox1.DataField then

    DBLookupComboBox1.Visible := False

end;

Agora, note que no modo de edição, todas as teclas pressionadas são encaminhadas para a célula do DBGrid, temos que redirecioná-las para o DBLookupComboBox. No caso de um DBLookupComboBox, estamos principalmente interessados nas teclas [Tab] ([Tab] deveria mover o foco de entrada para a próxima célula).


procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);

begin

  if (key = Chr(9)) then

    Exit;

  if (DBGrid1.SelectedField.FieldName = DBLookupComboBox1.DataField) then

  begin

    DBLookupComboBox1.SetFocus;

    SendMessage(DBLookupComboBox1.Handle, WM_Char, word(Key), 0);

  end

end;

Isso é tudo. Rode o projeto e voila... Temos um drop-down listbox (com duas colunas!) nas colunas do campo AuthorEmail.

O que acontece quando você seleciona um valor (Nome; E-mail) da drop-down list? Quando você escolhe um item ("row") de um DBLookupComboBox, o valor ou o campo KeyField correspondente é armazenado como o valor do campo DataField.