1 de dezembro de 2009

Algumas dicas para usar o TStringGrid

Esses dias tenho trabalhado num novo projeto para a ABC71 e no ponto atual tive que mexer bastante com String Grids. No começo do mês, publiquei sobre a customização da aparência deles usando o Canvas associado ao grid e o evento OnDrawCell. O que vou mostrar aqui é um pouco mais próximo do funcionamento básico do String Grid.

A proposta mais básica do String Grid é exibir textos e, para tanto, temos que colocar os textos desejados na propriedade Cells. Essa é uma propriedade indexada pela linha e coluna da célula de modo que é possível associar um valor para cada combinação de linha/coluna.
sg.ColCount := 2;
sg.RowCount := 5;
{ Primeiro é a coluna, depois a linha }
sg.Cells [0,0] := 'Módulo';
sg.Cells [1,0] := 'Responsável';

{ comprimento em pixels da coluna 'Módulo' }
sg.ColWidths [0] := 130;
{ comprimento em pixels da coluna 'Responsável' }
sg.ColWidths [1] := 240;
{ Altura de cada célula }
sg.DefaultRowHeight := 18;
{ configuração do Grid - inclui permissão para que o usuário edite valores diretamente no Grid }
sg.Options := [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goEditing];

Também é possível associar todo um objeto a cada célula e trabalhar com ele no evento OnDrawCell. Esse recurso permite enriquecer a forma com que os dados são apresentados, incluindo imagens, cores, textos com formatações diferenciadas na mesma célula, etc.A propriedade que permite criar esse tipo de associação é a Objects. Da mesma maneira que o Cells, o Objects também é acessível por linha e coluna, garantindo que cada célula possa ser associada a seu próprio conjunto de informações.

Como o Objects é do tipo TObject e todas as classes em Delphi são heranças dela, podemos associar nesta propriedade a instância de qualquer classe. Em C++ Builder, a herança deve ser explicitamente declarada. No trecho de código abaixo, a instância diferente de uma classe de informações é associada a cada uma das células da coluna 0 (zero).
var i : Integer;
Obj : TWInfoModulo;
begin
for i := 0 to Grupo.Count - 1 do begin
Obj := Grupo.ModuloAt(i);
sg.Objects [0,i+1] := Obj;
end;
end;

A associação poderia ter sido feita em cada uma das células do Grid se fosse necessário. No exemplo, entretanto, cada linha é relativa a apenas um objeto e por isso associei-o a apenas uma coluna e usei a informação compartilhada com as demais colunas da linha. Essa informação pode, então, ser usada no OnDrawCell para customizar a aparência do Grid.

Na verdade, usar o Objects é útil em qualquer um dos demais eventos. Por exemplo, para obter tanto a formatação da entrada de dados quanto os próprios valores para a edição do texto em cada uma das células, além da validação dos valores entrados pelo usuário:
procedure TWCenaRespons.DoOnSgCellChange (Sender: TObject; ACol, ARow: Longint; const Value: string);
var sg : TStringGrid;
lValue : String;
Obj : TWInfoModulo;
begin
sg := Sender As TStringGrid;

{ Só faz a verificação quando a célula sai do modo de edição }
if not sg.EditorMode then begin
lValue := UpperCase (Trim (Value));
Obj := sg.Objects [0,ARow] As TWInfoModulo;

{ Se não é um valor válido, retorna ao modo de edição na mesma linha/coluna }
if not Obj.ResponsavelValido (lValue) then begin
sg.Row := ARow;
sg.Col := ACol;
sg.EditorMode := true;
end;
end;
end;

O evento OnCellChange é acionado a cada novo caractere introduzido ou removido pelo usuário quando editando o valor de uma célula. Isso exige alguns cuidados para construir a resposta ao evento. Na resposta reproduzida acima, por exemplo, eu testo antes se o Grid está em modo de edição e só faço a validação do texto entrado pelo usuário quando a edição termina.

Ou seja, o evento também é acionado quando o editor perde o foco por qualquer razão e, nesta situação, a propriedade EditorMode do Grid é ajustada para falso, indicando que a célula não está mais em modo de edição. Caso o novo valor digitado pelo usuário seja inválido, uso essa mesma propriedade para forçar que uma nova entrada seja digitada para corrigir o problema.

32 comentários :

Tecnologia MG disse...

GOSTARIA DE SABER COMO FAÇO TIPO TENHO UM EDIT UM BOTÃO E UM STRINGGRID E QUERO DIGITAR NO EDIT E IR ADICONADO NO STRINGGRID AE EU CLICO NA COLUNA E DIGITO O QUE EU QUERO COMO FAÇO ISSO POR FAVOR URGENTE OBRIGADO

Luís Gustavo Fabbro disse...

Silvio

Para acrescentar uma nova linha ou coluna ao string grid use as propriedades ColCount e RowCount. Ou seja, a resposta ao clique do seu botão deve incrementar um desses valores e preencher o valor de Cells com o texto do Edit. Veja exemplos dessas atribuições no primeiro quadro desse post.

Quanto à edição direta numa célula, o String Grid tem um opção mais ou menos automática, ligando-se a opção goEditing na propriedade Options. Se quiser mais flexibilidade e controle na edição, pode criar um edit "na mão" inicialmente invisível. Depois, torne-o visível e posicione-o sobre a célula que desejar editar qdo for necessária a edição, dando a impressão de que se está editando a própria célula diretamente.

Anônimo disse...

Olá Luis, boa noite!
Estou trabalhando com um StringGrid com 10 colunas, e todas tem um Edit que criei dinamicamente posicionado conforme o Grid.CellRect(coluna, linha).Left de cada coluna.

Os Edits só ficam visíveis quando ele clicar no botão Editar.

O problema é que meu cliente possui resolução menor que a minha, e só cabem 6 colunas. E quando ele usa a barra de rolagem, as colunas movem, mas os edits não.

Tentei usar o evento OnHorizontalScroll (do TJvStringGrid - do Jedi 3 jcl/jvcl), mas não estou conseguindo fazer os edits navegar junto com as colunas.

Obrigado.
Luis - xará

Luís Gustavo Fabbro disse...

Luís

Fiz um teste baseado no que entendi do seu comentário mas não tive dificuldades em fazer o Edit acompanhar a rolagem do grid - nós usamos isso em nosso sistema na ABC71.

Já que você não postou o código, a única coisa que me vem à cabeça como causa do problema é que o Parent do Edit não está configurado com o StringGrid mas com algum outro componente (ou o Form). Se é esse o caso, experimente criar o Edit com algo como seque:
FEdit := TEdit.Create(Self);
FEdit.Visible := false;
FEdit.Parent := _MeuStringGrid;


Se ainda assim você não conseguir o efeito desejado, envie seu código para o email do blog (
balaiotecnologico@gmail.com
).

Anônimo disse...

Bom dia Luís,

Meu nome é Samuel sou iniciante no ramo de programação e também no Delphi e acabei de entrar em uma empresa, e recebi um serviço no qual tenho que criar uma espécie de agenda telefônica, busco no banco de dados o nome e a razão social das empresas cadastradas e mostro em um DBGrid, até essa parte está tudo bem, mas preciso de colocar um StringGrid ao lado, pois quando algum cliente for selecionado na lista, deve aparecer no StringGrid os seguintes dados

NOME para contato, DEPARTAMENTO, TELEFONE 1, TELEFONE 2 e o E-MAIL para contato.

Tenho todos estes dados disponíveis no banco de dados, mas não tenho nenhum conhecimento em StrigGrids.

O que você poderia me indicar?

Desde já agradeço.

Abraçps

Luís Gustavo Fabbro disse...

Samuel

O primeiro quadro no post mostra como fazer a configuração básica do TStringGrid e como acessar as células individuais para ler e gravar informações. Onde vc "enroscou" nesses conceitos?

[]s

Anônimo disse...

Correto Luís, eu peguei esse código e joguei no meu projeto, mas não consigo entender como fazer para ele reconhecer meu dados do baco de dados, por que eu sei q como a TStringGrid e feita toda manual podemos jogar as informações que quisermos, mas não sei como buscá-las no banco. Como disse, não tenho conhecimento nenhum em TStrigGrid, pois no meu curso não vi este componente.

Anônimo disse...

Olá Luís, aqui é o Samuel de novo.

Bom, para ficar mais detalhada minha dúvida, preciso pegar os seguintes campos que estão no banco de dados, na tabela Pessoa:
PESSOA.FONE_CEL_PESSOA,
PESSOA.FONE_SEC_PESSOA,
PESSOA.FONE_CONTATO_ENT_PESSOA,
PESSOA.FONE_CONTATO_FAT_PESSOA,
PESSOA.FONE_PRIM_PESSOA,
PESSOA.EMAIL_CONTATO_PESSOA,
PESSOA.EMAIL_CONTATO_ENT_PESSOA,
PESSOA.EMAIL_CONTATO_FAT_PESSOA,
PESSOA.NOME_CONTATO_ENT_PESSOA,
PESSOA.NOME_CONTATO_FAT_PESSOA,
PESSOA.NOME_CONTATO_PESSOA,
PESSOA.DEPTO_CONTATO_ENT_ENTREGA,
PESSOA.DEPTO_CONTATO_FAT_ENTREGA,
PESSOA.DEPTO_CONTATO_PESSOA

e devido a minha falta de experiência e conhecimento com TStringGrid, não sei como posso fazer.

Luís Gustavo Fabbro disse...

Samuel

Quando você seleciona uma linha no DBGrid, a infraestrutura da VCL automaticamente posiciona o registro no DataSource vinculado. Assim, basta vc acessar o valor do campo a partir do próprio DataSet, obtendo o valor dele para o registro atual.

Supondo que seja um AdoQuery, por exemplo, vc obteria o valor com o comando :
qr.FieldByName ('EMAIL_CONTATO_PESSOA').AsString

Se você inseriu componentes para os campos no adoquery, basta escrever :
qrEMAIL_CONTATO_PESSOA.Value
para recuperar o valor do campo no registro atual.

[]s

Anônimo disse...

Tudo bem Luís, vou tentar aqui dentro das minhas limitações, e dou uma resposta, positiva ou negativa, de qualquer forma obrigado pela ajuda, se não conseguir volto aqui novamente.

Anônimo disse...

Olá Luís, aqui é novamente o Samuel, não estou conseguindo resolver meu problema, na verdade esta e o meu primeiro serviço nesta empresa em que estou, e eu já havia feito esse projeto, mas obtive a ajuda do outro programador da empresa, principalmente na parte da stringgrid, porém a única coisa que eu não consegui aprender foi a parte da stringgrid, lembro-me que ele fez um monte de coisas, mexeu nos parâmetros, lembro-me que teve uma parte em que ele colocou todos estes campos que eu preciso no meio dos códigos, ele digitou algum comando e foi colocando os campos um abaixo do outro, tudo isso ele fez acima desta parte dos códigos:

sg.ColCount := 2;
sg.RowCount := 5;
{ Primeiro é a coluna, depois a linha }
sg.Cells [0,0] := 'Módulo';
sg.Cells [1,0] := 'Responsável';

pois no cells você coloca o que vai aparecer estou certo? mas se eu simplesmente digitar lá
sg.Cells [1,0] := 'EMAIL_CONTATO_PESSOA'
ele não vai buscar no banco, e sim vai exibir no grid escrito desta forma, 'EMAIL_CONTATO_PESSOA' estou precisando muito fazer isso mas não tenho a mínima ideia de como faço isso. Obrigado pela atenção e desculpe se estou "amolando" demais.

Luís Gustavo Fabbro disse...

Samuel

Como eu disse no outro comentário, basta recuperar o valor dos campos do registro atual; o próprio ambiente posicionará o registro pra você sempre que mudar a linha selecionada no DBGrid. Supondo que qr seja a query associada ao DBGrid, lançar o email na primeira linha e coluna do string grid é fazer a seguinte atribuição:

sg.Cells [0, 1] := qr.FieldByName ('EMAIL_CONTATO_PESSOA').AsString;

Essa atribuição deve fazer parte da resposta ao evento AfterScroll da query em questão.

[]s

Anônimo disse...

Samuel.

No meu caso eu nao uso query, eu uso clientdataset, como ficaria Luís?

Luís Gustavo Fabbro disse...

O ClientDataSet compartilha diversas funcionalidades com o AdoQuery (ambos são heranças de TDataSet). Portanto, a forma de acessar os campos é a mesma:

sg.Cells[0,1] := cds.FieldByName(nomeDoCampo).AsString;

[]s

Anônimo disse...

Samuel.

Luís consegui, fiz um pouco diferente, e deu certo, muito obrigado pela ajuda, eu fiz desta forma no Ondatachange do data source:
sg.Cells[0,1]:=DMPesquisa.cdsPessoaNOME_CONTATO_PESSOA.AsString

Anônimo disse...

Luís, agora estou com apenas uma dúvida, não consigo editar a mask da stringgrid, preciso editar as duas colunas de telefone, se possível você poderia me mandar o que eu coloco e em qual lugar eu coloco, se possível até um exemplo, agradeço.

Samuel.

Luís Gustavo Fabbro disse...

Máscaras de edição podem ser fornecidas individualmente através de resposta ao evento OnGetEditMask.

[]s

Anônimo disse...

ola Luis, aqui é o Samuel consegui configurar a mask dos telefones, agora estou com dificuldade, pois que q a mascara apareça apenas o cliente tiver telefone, pois alguns telefones estão faltando, e gostaria que não aparecesse os parenteses e o hífen, será que você poderia me mandar este código?

sg.Cells[2,2] := FormatMaskText('!(99)9999-9999;0;', DMPesquisa.cdsPessoaFONE_CONTATO_ENT_PESSOA.AsString);

Luís Gustavo Fabbro disse...

Samuel

Antes de fazer a atribuição, teste se o campo "telefone" está com valor. Se estiver zerado, simplesmente não faça a atribuição.

[]s

Unknown disse...

perdão Luís, mas como eu faço isso?

Luís Gustavo Fabbro disse...

Seria algo como segue :

With DMPesquisa.cdsPessoaFONE_CONTATO_ENT_PESSOA do
if (AsInteger = 0) then
sg.Cells[2,2] := ''
else
sg.Cells[2,2] :=
FormatMaskText('!(99)9999-9999;0;', AsString);

[]s

Unknown disse...

Olá luis, estou tentando este código que você me passou, porém estou com um problema, o meu campo não é AsInteger, e sim AsString, e quando eu mudo o if de AsInteger para AsString ele nao executa o programa, da um erro:

[DCC Error] unitAgenda.pas(81): E2010 Incompatible types: 'string' and 'Integer'

Unknown disse...

Luis consegui, o erro era esse:

eu tinha q alterar para AsString
if (AsInteger = 0) then

mas eu colocava assim
if (AsString = 0) then

e na verdade tinha q ser assim
if (AsString = '') then

Obrigado.

Anônimo disse...

Olá, tentei utilizar esta dica, mas estou usando o Delphi 7 e não existe o evento OnCellChange no StringGrid. Alguma outra dica de como controlar a alteração das células?
Obrigado.

Gustavo

Luís Gustavo Fabbro disse...

Gustavo

Você pode fazer uma jogada com o evento OnSelectCell. Ele é disparado imediatamente antes de uma nova célula ser selecionada. Com isso, você pode impedir a nova seleção se o valor da célula atual for inválido. O parâmetro CanSelect permite controlar se a nova célula pode ser selecionada.

O OnExit do grid pode ser usado para completar o tratamento, impedindo o grid como um todo de perder o foco se houver algum valor inválido em suas células.

[]s

Anônimo disse...

Deus te abençoe... Seu Post vai me ajudar muito. Obrigado

Marcos - Fortaleza

Anônimo disse...

Luís ,

Estou fazendo uma forca e gostaria de saber como colocar uma string (palavraSorteada) em uma string grid (caracter pro caracter) e examinar, ao clique num botão de uma letra qualquer, se a string grid possui este caracter.

Desde já agradeço,

Kévin

Luís Gustavo Fabbro disse...

Kevin

Pela sua descrição, imagino que o grid terá uma palavra por linha e que cada letra da palavra ocupará uma coluna na linha.

Se for isso mesmo, crie uma estrutura contendo a letra da célula e um indicador se essa letra já foi selecionada. Associe uma instância dessa estrutura ao Objects de cada célula e responda ao evento OnDrawCell pintando somente as letras que já tenham sido selecionadas.

No clique do botão com uma letra, monte um laço FOR para acessar o Objects das células, marcando aquelas cuja letra é igual à do botão pressionado. Leve em conta que as letras podem ter acentuação ou cedilha.

[]s

Unknown disse...

Boa tarde Luis

Gostaria de saber como que conseguiria quebrar linha na celula do StringGrid.
Por exemplo tempo os seguintes dados do banco e mostrar eles em mais de 1 linha na celula. Por exemplo:

Nome: FULANO DE TAL
Endereço: ENDEREÇO DO FULANO
Telefone: TELEFONE DO FULADO

Tentei utilziar o #13 mas não dá certo

Luís Gustavo Fabbro disse...

Fernando

O TStringGrid não faz essa quebra de linhas automática dentro de uma célula. Você pode responder ao evento OnDrawCell e trabalhar com o canvas da célula para produzir manualmente esse efeito. Há alguns posts no blog tratando disso; veja Customizando a aparência de um Grid em Delphi - parte 1, parte 2 e parte 3.

[]s

Unknown disse...

Eu tenho um Edit e um Button para pesquisa, só que os dados só aparecem quando eu dou um clique no StringGrid, o que faço para aparecer imediatamente quando clico no Button?

Luís Gustavo Fabbro disse...

Alexandre

A única coisa imprescindível a se fazer para que os textos apareçam no string grid é preencher as células (propriedade Cells) com o valor desejado.

Se customizou a exibição das células, revise o código para garantir que o evento está desenhando todas as células - mesmo aquelas que deveriam ter o desenho padrão.

[]

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.