O que voce vai encontrar neste artigo:
- Uma função genérica para controlar a edição de registros;
- Criar um TTABLE em tempo de execução;
- Validar duplicação de chave;
- Utilizar os eventos OnDataChange e OnStateChange do componenteTDatasource;
- Controlar os estados de uma tabela;
- Pesquisa de campo chave;
- Permitir ou não o fechamento do form;
Verificar o estado de uma tabela pode
ser muito interessante para o programador em geral, alguns estados podem ser de
grande valia para determinar se alguma atitude deve ser tomada pelo programa.
Abaixo esta uma tabela com todos os estados possíveis para o componente TTable.
Estado | Descrição
------------------------------------------------------------------
dsInactive | Tabela não esta aberta
dsBrowse | Esta em navegação
dsEdit | Registro esta em edição
dsInsert | Inserindo um novo registro
dsSetKey | Estabelecendo valores para os campos chaves do arquivo, em uma
operação de faixas ou pesquisa
dsCalcFields | Obtendo valores para os campos calculados
dsUpdateNew | Reservado para uso interno
dsUpdateOld | Reservado para uso interno
dsFilter | Processando o evento OnFilterRecord
------------------------------------------------------------------
Estes estados podem ser críticos em situações como edição de registros já
existentes ou inclusão de novos registros.
· dsInactive indica que a tabela não
esta aberta, mas também pode indicar que um processo de abertura não foi bem
sucedido.
· dsEdit indica que um registro que já existia na tabela esta agora sofrendo uma
alteração, em conjunto com a propriedade Modified podemos montar uma rotina de
tratamento muito completa para um possível cancelamento da edição.
· dsInsert indica que estamos trabalhando com um novo registro, não importa se
este registro tenha sido criado com Append ou com Insert.
· dsSetKey indica que estamos informando uma chave para pesquisa.
Estes provavelmente devem ser os mais
utilizados...
Abaixo um exemplo de como os estados da tabela devem ser controlados.

Nesta última revisão foram incluídos
os procedimentos necessários para executar uma pesquisa dentro do índice
atualmente em uso na tabela que é por código.
O código deste projeto está bem mais coerente com as reais necessidades de um
form de edição. Os aspectos novos que vamos ver são muito portáveis, podendo ser
utilizados em qualquer projeto no dia a dia.
O primeiro procedimento do form apresenta uma característica das units em Object
Pascal que ainda não exploramos, declarar um procedimento local não diretamente
ligado ao Form em que está sendo inserido.
Na declaração a função não tem o seu nome precedido pelo nome do form e tão
pouco está incluída na declaração de procedimentos do form.
Este tipo de procedimento/função será visível dentro da unit que está sendo
colocada e deve ser posicionada antes de qualquer referencia a ela, para que o
compilador a reconheça sem problemas, é o primeiro passo para a criação de uma
biblioteca.
Outro item a ser observado é como se passa uma Table como parâmetro para uma
função, e como esta função pode manipular de forma transparente esta Table sem
que se tenha a menor idéia de onde ela tenha vindo.
Foi necessário tratar a situação de verificação do estado de uma Table, e
conseqüentemente a atitude a ser tomada, dentro de uma função para evitar a
duplicação de código idêntico em pontos distintos do projeto, quando o usuário
tenta fechar o form estando em edição ou inserção, quando o usuário seleciona o
botão "Cancela" ou ainda quando a tabela esta esperando a entrada da chave para
pesquisa.
{--------------------------------------------------------------}
{Uma função local }
{--------------------------------------------------------------}
{----------}
procedure CheckState(ATable: TTable);
{----------}
var
strMessage : String[25];
wrdMessageResult : word;
begin
ATable.UpdateRecord; { Atualiza a tabela sem gravar }
if ( ATable.Modified ) and
( ATable.State <> dsSetKey ) then
begin
if ATable.State = dsEdit then
strMessage := 'Editando registro, ';
if ATable.State = dsInsert then
strMessage := 'Inserindo registro, ';
wrdMessageResult := MessageDlg(strMessage+ 'as informações
que ainda não '+ 'foram salvas serão perdidas!'+#10+ 'Salva as informações ?',
mtConfirmation, [mbYes,mbNo,mbCancel],0);
case wrdMessageResult of
mrCancel: Abort;
mrYes : ATable.Post;
mrNo : ATable.Cancel;
end;
end
else
ATable.Cancel;
end;
As três respostas mais comuns, para
Cancelar a operação em curso é utilizado o procedimento Abort, caso a resposta
seja mrYes então os dados da tabela são gravados e se for mrNo a edição ou
inserção será cancelada.
Na linha que utilizamos a função MessageDlg a linha de mensagem que foi passada
continha um caractere ASCII(10)para forçar a quebra de linha.

wrdMessageResult :=
MessageDlg(strMessage+ 'as informações que ainda não '+ 'foram salvas serão
perdidas!'+#10+ 'Salva as informações ?', mtConfirmation,
[mbYes,mbNo,mbCancel],0);
Fica mais fácil se você controlar a quebra de linhas quando a mensagem a ser
informada ocupar mais de uma linha, normalmente a quebra oferecida pelo Delphi
deixa muito a desejar.
O procedimento associado ao evento OnClick do botão "Cancela" utiliza o
procedimento CheckState, muito pratico,já que esta lá vamos usar ...
procedure
TForm1.btnCancelaClick(Sender: TObject);
begin
CheckState(tblCadcli);
end;
Evento OnDataChange do Datasource.
Utilizamos este evento para determinar quando houve uma movimentação da
tabela,assim é possível ligar ou desligar os botões de movimentação para o
usuário.
procedure TForm1.dtsCadcliDataChange(Sender: TObject; Field: TField);
var
localTable : TTable;
begin
if Field <> nil then
exit;
localTable := ((sender as TDatasource).Dataset as TTable);
btnNext.enabled := not localTable.eof;
btnLast.enabled := not localTable.eof;
btnPrior.enabled := not localTable.bof;
btnFirst.enabled := not localTable.bof;
end;
Evento OnStateChange do Datasource.
Este parte do código foi bem alterada, primeiro as mensagens ao usuário foram
melhoradas e os botões estão sendo controlados apenas quando realmente o estado
da tabela se altera assim como a disponibilidade ou não dos TDBEdit's
envolvidos.
Quando em edição o TDBEdit para edição do código torna-se indisponível para
impedir a alteração do campo chave.
Também neste evento são executadas as alterações necessárias na interface para
que o usuário possa entrar com a chave para pesquisa.
procedure
TForm1.dtsCadcliStateChange(Sender: TObject);
var
localState : TDataSetState;
begin
localState := (Sender as TDatasource).State;
btnPesquisa.enabled := (LocalState in [dsBrowse, dsSetKey]);
{ Botão de pesquisa }
btnGrava.enabled := not(LocalState in [dsBrowse, dsSetKey]);
btnCancela.enabled := not(LocalState in [dsBrowse]);
btnNovo.enabled := (LocalState = dsBrowse);
btnEdita.enabled := (LocalState = dsBrowse);
{ Controla a mensagem do display }
case localState of
dsBrowse: begin
btnPesquisa.Caption:= 'Pesquisa';
edtCODIGO.color := clWindow;
Display.Font.Color := clBlue;
Display.Caption := 'Navegando';
end;
dsEdit : begin
Display.Font.Color := clRed;
Display.Caption :=
'Editando registro';
end;
dsInsert: begin
Display.Font.Color := clRed;
Display.Caption := 'Inserindo registro';
end;
dsSetKey: begin
btnPesquisa.Caption:= 'Executa ...';
edtCODIGO.color := clYellow;
Display.Font.Color := clRed;
Display.Caption := 'Pesquisando registro, ESC Cancela';
end;
end;
{ O campo de código só pode ser acessado em inclusão }
edtCodigo.Enabled := LocalState in [dsInsert, dsSetKey];
if LocalState in [dsInsert, dsSetKey] then
if edtCodigo.Canfocus then
edtCodigo.SetFocus;
if LocalState = dsEdit then
if edtNome.CanFocus then
edtNome.SetFocus;
end;
O botão de pesquisa foi incluido na interface e dispara o seguinte
procedimento...
procedure TForm1.btnPesquisaClick(Sender: TObject);
var
strChave : string;
begin
with tblCadCli do
case State of
dsBrowse : SetKey;
dsSetKey : begin
strChave := edtCodigo.Text; { Guarda a chave para pesquisa
aproximada }
if Trim( strChave ) = '' then
begin
cancel;
exit;
end;
if not GotoKey then
if MessageDlg('Não foi encontrada a chave '+strChave+'
!'+#13+ 'Utiliza o registro mais próximo à chave ?',
mtConfirmation,[mbYes, mbNO],0) = mrYes then
begin
SetKey;
FieldByName('CODIGO').AsString := strChave;
GotoNearest;
end;
end;
end;
end;
Caso a chave informada não seja
encontrada no arquivo o usuário é questionado se deseja parar no registro mais
próximo.
O Evento OnCloseQuery do form é disparado imediatamente antes de se fechar o
form e é a sua ultima chance de impedir este fechamento.
No projeto de exemplo onCloseQuery chama o procedimento CheckState quando a
tabela do form estiver em estado de edição ou inserção.
Para impedir que o form seja fechado pode-se utilizar a variável CanClose,
passada ao procedimento com "VAR", se for atribuído o valor false a esta
variável o form não será fechado, ou gerando uma exception que no nosso caso é
gerada dentro de CheckState se o usuário escolher o botão "Cancel".
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if (tblCadcli.State in [dsEdit, dsInsert]) then
CheckState(tblCadcli);
end;
Abaixo segue o código completo da
unit...
==================================================================
unit Unit1;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics,
StdCtrls, Forms, DBCtrls, DB, Mask, Buttons, DBTables, ExtCtrls,
Controls;
type
TForm1 = class(TForm)
Panel1: TPanel;
dtsCadcli: TDataSource;
tblCadcli: TTable;
btnFirst: TSpeedButton;
btnPrior: TSpeedButton;
btnNext: TSpeedButton;
btnLast: TSpeedButton;
btnNovo: TButton;
btnEdita: TButton;
btnGrava: TButton;
btnCancela: TButton;
Panel2: TPanel;
Label1: TLabel;
edtCODIGO: TDBEdit;
edtNOME: TDBEdit;
edtENDERECO: TDBEdit;
edtCONTATO: TDBEdit;
edtTELEFONE: TDBEdit;
Display: TLabel;
btnPesquisa: TSpeedButton;
procedure FormCreate(Sender: TObject);
procedure btnFirstClick(Sender: TObject);
procedure btnPriorClick(Sender: TObject);
procedure btnNextClick(Sender: TObject);
procedure btnLastClick(Sender: TObject);
procedure btnNovoClick(Sender: TObject);
procedure btnEditaClick(Sender: TObject);
procedure btnGravaClick(Sender: TObject);
procedure btnCancelaClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure dtsCadcliDataChange(Sender: TObject; Field: TField);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure dtsCadcliStateChange(Sender: TObject);
procedure btnPesquisaClick(Sender: TObject);
procedure edtCODIGOKeyPress(Sender: TObject; var Key: Char);
procedure tblCadcliBeforeOpen(DataSet: TDataSet);
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
uses Dialogs;
{Uma função local }
procedure CheckState(ATable: TTable);
var
strMessage : String[25];
wrdMessageResult : word;
begin
ATable.UpdateRecord; { Atualiza a tabela sem gravar }
if ( ATable.Modified ) and ( ATable.State <> dsSetKey ) then
begin
if ATable.State = dsEdit then
strMessage := 'Editando registro, ';
if ATable.State = dsInsert then
strMessage := 'Inserindo registro, ';
wrdMessageResult := MessageDlg(strMessage+ 'as informações que ainda não
'+ 'foram salvas serão perdidas!'+#10+ 'Salva as informações ?', mtConfirmation,
[mbYes,mbNo,mbCancel],0);
case wrdMessageResult of
mrCancel: Abort;
mrYes : ATable.Post;
mrNo : ATable.Cancel;
end;
end
else
ATable.Cancel;
end;
{--------------------------------------------------------------}
{FormCreate, FormDestroy }
procedure TForm1.FormCreate(Sender: TObject);
begin
tblCadcli.Open;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
tblCadcli.Close;
end;
{--------------------------------------------------------------}
{Movimentação (First,Prior,Next,Last) }
procedure TForm1.btnFirstClick(Sender: TObject);
begin
tblCadcli.First;
end;
procedure TForm1.btnPriorClick(Sender: TObject);
begin
tblCadcli.Prior;
end;
procedure TForm1.btnNextClick(Sender: TObject);
begin
tblCadcli.Next;
end;
procedure TForm1.btnLastClick(Sender: TObject);
begin
tblCadcli.Last;
end;
{Operações (Novo,Edita,Grava,Cancela) }
procedure TForm1.btnNovoClick(Sender: TObject);
begin
tblCadcli.Append;
end;
procedure TForm1.btnEditaClick(Sender: TObject);
begin
tblCadcli.Edit;
end;
procedure TForm1.btnCancelaClick(Sender: TObject);
begin
CheckState(tblCadcli);
end;
procedure TForm1.btnGravaClick(Sender: TObject);
var
tblAux : TTable;
bolKeyViolation : Boolean;
begin
if tblCadcli.State = dsInsert then
begin
{ cria uma nova TTable em tempo de execução }
tblAux := TTable.create(self);
with tblAux do
begin
DataBaseName := tblCadcli.DataBaseName;
TableName := tblCadcli.TableName;
ReadOnly := true;
Active := true;
SetKey;
FieldByName('Codigo').AsString := edtCodigo.text;
bolKeyViolation := GotoKey;
Close;
Free;
if bolKeyViolation then
raise Exception.Create('O codigo
'+edtCodigo.text+' já existe no arquivo !');
end;
end;
tblCadcli.Post;
end;
{--------------------------------------------------------------}
{OnDataChange }
{OnStateChange }
procedure TForm1.dtsCadcliDataChange(Sender: TObject; Field: TField);
var
localTable : TTable;
begin
if Field <> nil then
exit;
localTable := ((sender as TDatasource).Dataset as TTable);
btnNext.enabled := not localTable.eof;
btnLast.enabled := not localTable.eof;
btnPrior.enabled := not localTable.bof;
btnFirst.enabled := not localTable.bof;
end;
procedure TForm1.dtsCadcliStateChange(Sender: TObject);
var
localState : TDataSetState;
begin
localState := (Sender as TDatasource).State;
btnPesquisa.enabled := (LocalState in [dsBrowse, dsSetKey]); { Botão de
pesquisa }
btnGrava.enabled := not(LocalState in [dsBrowse, dsSetKey]);
btnCancela.enabled := not(LocalState in [dsBrowse]);
btnNovo.enabled := (LocalState = dsBrowse);
btnEdita.enabled := (LocalState = dsBrowse);
{ Controla a mensagem do display }
case localState of
dsBrowse: begin
btnPesquisa.Caption:= 'Pesquisa';
edtCODIGO.color := clWindow;
Display.Font.Color := clBlue;
Display.Caption := 'Navegando';
end;
dsEdit : begin
Display.Font.Color := clRed;
Display.Caption := 'Editando registro';
end;
dsInsert: begin
Display.Font.Color := clRed;
Display.Caption := 'Inserindo registro';
end;
dsSetKey: begin
btnPesquisa.Caption:= 'Executa ...';
edtCODIGO.color := clYellow;
Display.Font.Color := clRed;
Display.Caption := 'Pesquisando registro, ESC Cancela';
end;
end;
{ O campo de codigo só pode ser acessado em inclusão }
edtCodigo.Enabled := LocalState in [dsInsert, dsSetKey];
if LocalState in [dsInsert, dsSetKey] then
if edtCodigo.Canfocus then
edtCodigo.SetFocus;
if LocalState = dsEdit then
if edtNome.CanFocus then
edtNome.SetFocus;
end;
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if (tblCadcli.State in [dsEdit, dsInsert]) then
CheckState(tblCadcli);
end;
procedure TForm1.btnPesquisaClick(Sender: TObject);
var
strChave : string;
begin
with tblCadCli do
case State of
dsBrowse : SetKey;
dsSetKey : begin
strChave := edtCodigo.Text; { Guarda a chave para pesquisa aproximada }
if Trim( strChave ) = '' then
begin
cancel;
exit;
end;
if not GotoKey then
if MessageDlg('Não foi encontrada a chave '+strChave+'
!'+#13+ 'Utiliza o registro mais próximo à chave ?',
mtConfirmation,[mbYes, mbNO],0) = mrYes then
begin
SetKey;
FieldByName('CODIGO').AsString := strChave;
GotoNearest;
end;
end;
end;
end;
procedure TForm1.edtCODIGOKeyPress(Sender: TObject; var Key: Char);
begin
if tblCadcli.State = dsSetKey then
case key of
#13 : btnPesquisa.click;
#27 : btnCancela.Click;
end;
end;
procedure TForm1.tblCadcliBeforeOpen(DataSet: TDataSet);
begin
{ Pega o arquivo do diretório da aplicação }
(Dataset as TTable).DataBaseName:=ExtractFilePath(Application.ExeName);
end;
end.
Observação: Paulo Henrique Altino Dias - phdias@netsite.com.br
|