1 {Frame que contiene utilidades a realizar sobre grillas TSTringGrid. Actualmente solo se
2 ha implementado la opción de búsqueda.
3 Para realizar su trabajo, se debe asociar a un TStringGrid cualquiera. Notar que no es
4 necesario usar el objeto TUtilGrilla o TUtilGrillaFil.
5 Para facilitar el uso de este frame, se ha definido que no tenga más dependencias
6 especiales que BasicGrilla.
7 Además no se debe interceptar eventos de la grilla, a menos que sea estrcitamente necesario.
8 Estos se hace previeniendo el caso de que la grilla necesite usar sus eventos o estos estén
9 siendo usados por alguna otra utilidad.
10 
11 La forma de utilizar el frame, es insertándolo en el formulario o panel, a modo de
12 ToolBar. Luego configurarlo:
13 
14 fraUtilsGrilla1.Inic(grilla);
15 fraUtilsGrilla1.AgregarColumnaFiltro('Por Código', 1);
16 fraUtilsGrilla1.AgregarColumnaFiltro('Por Nombre', 2);
17 
18 También se debe llamar a fraUtilsGrilla1.GridKeyPress(Key), en el evento KeyPress() de la
19 grilla, si se desea que la búsqueda se haga con solo pulsar una tecla en la grilla.
20 
21                                                    Por Tito Hinostroza 28/04/016.
22 }
23 unit FrameFiltCampo;
24 {$mode objfpc}{$H+}
25 interface
26 uses
27   Classes, SysUtils, FileUtil, Forms, Controls, StdCtrls, ExtCtrls, LCLProc,
28   Graphics, Buttons, LCLType, Grids, fgl, UtilsGrilla;
29 type
30   TFiltroGrilla = class
31     etiq: string;   //etiqueta  a mostrar
32     campo: integer; //campo a usar como filtro
33   end;
34   TFiltroGrilla_list = specialize TFPGObjectList<TFiltroGrilla>;
35   //Modo de filtrado
36   TModFiltrado = (
37     mfilNone,    //Sin filtro (pasa todo)
38     mfil1Pal,    //COincidencia con 1 palabra
39     mfil2Pal,    //Coincidencia con 2 palabras
40     mfilIgualN,  //Comparación con valor
41     mfilMayorN,  //Comparación con valor
42     mfilMenorN,  //Comparación con valor
43     mfilMayorIN,  //Comparación con valor
44     mfilMenorIN,  //Comparación con valor
45     mfilDiferN   //Comparación con valor
46   );
47   { TfraFiltCampo }
48   TfraFiltCampo = class(TFrame)
49     ComboBox2: TComboBox;
50     Edit1: TEdit;
51     Panel1: TPanel;
52     btnFind: TSpeedButton;
53     btnClose: TSpeedButton;
54 
55     procedure btnCloseClick(Sender: TObject);
56 
57     procedure ComboBox2Change(Sender: TObject);
Filtronull58     function Filtro(const f: integer):boolean;
59     procedure Edit1Change(Sender: TObject);
60     procedure Edit1Enter(Sender: TObject);
61     procedure Edit1Exit(Sender: TObject);
62     procedure Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
63     procedure FrameResize(Sender: TObject);
64   private
65     grilla :TStringGrid;  //grilla asociada
66     proteger: boolean;
67     filtros : TFiltroGrilla_list;
68     campoAfiltrar: integer;
69     modFiltro: TModFiltrado;  //Modo del filtro
70     buscar1, buscar2: string;  //palabras de búsqueda
71     buscarN: double;  //búsqueda por cantidad
72     griFiltrar: TUtilGrilla;
73     procedure ActualizarVisibilidadBotones;
74     procedure fraFiltCampoCambiaFiltro;
75     procedure ModoConTexto(txt0: string);
76     procedure ModoSinTexto;
77     procedure PreparaFiltro;
PreparaCadnull78     function PreparaCad(const cad: string): string;
79   public
80     msjeBuscar  : string;   //mensajae inicial que aparecerá en el cuadro de texto
81     incluirCateg: boolean;
82     txtBusq     : string;  //Texto que se usa para la búsqueda
83     OnCambiaFiltro: procedure of object;  //Cuando cambia algún parámetro del filtro
84     procedure AgregarColumnaFiltro(NomCampo: string; ColCampo: integer);
85     procedure GridKeyPress(var Key: char);  //Para dejar al frame procesar el evento KeyPress de la grilla
SinTextonull86     function SinTexto: boolean;
87     procedure LeerCamposDeGrilla(cols: TGrillaDBCol_list; indCampoDef: integer);
88     procedure Activar(txtIni: string);
89     procedure SetFocus; override;
Focusednull90     function Focused: Boolean; override;
91   public  //Inicialización
92     procedure Inic(grilla0: TStringGrid); virtual;
93     procedure Inic(gri: TUtilGrilla; campoDef: integer=-1); virtual;
94     constructor Create(TheOwner: TComponent); override;
95     destructor Destroy; override;
96   end;
97 
98 implementation
99 {$R *.lfm}
100 { TfraFiltCampo }
101 procedure TfraFiltCampo.ActualizarVisibilidadBotones;
102 begin
103   if txtBusq = '' then begin
104     //No tiene contenido
105     btnFind.Visible:=true;
106     btnClose.Visible:=false;
107   end else begin
108     //Tiene contendio
109     btnFind.Visible:=false;
110     btnClose.Visible:=true;
111   end;
112   //ComboBox2.Enabled := filtros.Count>0;
113 end;
114 procedure TfraFiltCampo.ModoSinTexto;
115 //Pone al control de edición en modo "Esperando a que se escriba."
116 begin
117   proteger := true;
118   Edit1.Font.Italic := true;
119   Edit1.Font.Color := clGray;
120   Edit1.Text := msjeBuscar;
121   proteger := false;
122 end;
123 procedure TfraFiltCampo.ModoConTexto(txt0: string);
124 begin
125   proteger := true;
126   Edit1.Font.Italic := false;
127   Edit1.Font.Color := clBlack;
128   Edit1.Text:=txt0;
129   proteger := false;
130 end;
TfraFiltCampo.SinTextonull131 function TfraFiltCampo.SinTexto: boolean;
132 {Indica si el control está sin texto de búsqueda.}
133 begin
134   Result := txtBusq='';
135 end;
136 procedure TfraFiltCampo.AgregarColumnaFiltro(NomCampo: string;
137   ColCampo: integer);
138 //Agrega un campo para usar como filtro a la lista
139 var
140   fil: TFiltroGrilla;
141 begin
142   fil := TFiltroGrilla.Create;
143   fil.etiq  := NomCampo;
144   fil.campo := ColCampo;
145   filtros.Add(fil);
146   ComboBox2.AddItem(NomCampo, nil);
147   ComboBox2.ItemIndex:=0;  //selecciona el primero
148 end;
149 procedure TfraFiltCampo.btnCloseClick(Sender: TObject);  //Limpia texto
150 begin
151   Edit1.Text:= '';
152 end;
PreparaCadnull153 function TfraFiltCampo.PreparaCad(const cad: string): string;
154 {Procesa una cadena y la deja lista para comparaciones}
155 begin
156   Result := Upcase(trim(cad));
157   //Esta parte  puede ser lenta
158   Result := StringReplace(Result, 'Á', 'A', [rfReplaceAll]);
159   Result := StringReplace(Result, 'É', 'E', [rfReplaceAll]);
160   Result := StringReplace(Result, 'Í', 'I', [rfReplaceAll]);
161   Result := StringReplace(Result, 'Ó', 'O', [rfReplaceAll]);
162   Result := StringReplace(Result, 'Ú', 'U', [rfReplaceAll]);
163   Result := StringReplace(Result, 'á', 'A', [rfReplaceAll]);
164   Result := StringReplace(Result, 'é', 'E', [rfReplaceAll]);
165   Result := StringReplace(Result, 'í', 'I', [rfReplaceAll]);
166   Result := StringReplace(Result, 'ó', 'O', [rfReplaceAll]);
167   Result := StringReplace(Result, 'ú', 'U', [rfReplaceAll]);
168 end;
Filtronull169 function TfraFiltCampo.Filtro(const f: integer): boolean;
170 {Rutina que implementa un filtro, de acuerdo al formato requerido por TUtilGrilla.
171 Esta rutina es llamada por cada fila de la grilla.}
ValidaFiltFil1null172   function ValidaFiltFil1(const busc: string; const buscar1: string): boolean;
173   begin
174     if Pos(buscar1, busc) <> 0 then begin
175       exit(true);
176     end else begin  //no coincide
177       exit(false);
178     end;
179   end;
ValidaFiltFil2null180   function ValidaFiltFil2(const busc: string; const buscar1, buscar2: string): boolean;
181   begin
182     if Pos(buscar1, busc) <> 0 then begin
183       //coincide la primera palabra, vemos la segunda
184       if Pos(buscar2, busc) <> 0 then begin
185         exit(true);
186       end else begin  //no coincide
187         exit(false);
188       end;
189     end else begin  //no coincide
190       exit(false);
191     end;
192   end;
193 var
194   tmp: String;
195   n: Double;
196 begin
197   case modFiltro of
198   mfilNone: exit(true);   //siempre pasa
199   mfil1Pal: begin  //búsqueda de una palabra
200     tmp := PreparaCad(grilla.Cells[campoAfiltrar, f]);
201     exit(ValidaFiltFil1(tmp, buscar1));
202   end;
203   mfil2Pal: begin  //búsqueda de dos palabras
204     tmp := PreparaCad(grilla.Cells[campoAfiltrar, f]);
205     exit(ValidaFiltFil2(tmp, buscar1, buscar2));
206     //se podría acelerar, si se evita pasar los parámetros "buscar1" y "buscar2".
207   end;
208   mfilIgualN: begin  //igual a número
209     if not TryStrToFloat(grilla.Cells[campoAfiltrar, f], n) then exit(false);
210     Result := (n = buscarN);
211   end;
212   mfilDiferN: begin
213     if not TryStrToFloat(grilla.Cells[campoAfiltrar, f], n) then exit(false);
214     Result := (n <> buscarN);
215   end;
216   mfilMenorN: begin  //igual a número
217     if not TryStrToFloat(grilla.Cells[campoAfiltrar, f], n) then exit(false);
218     Result := (n < buscarN);
219   end;
220   mfilMayorN: begin  //igual a número
221     if not TryStrToFloat(grilla.Cells[campoAfiltrar, f], n) then exit(false);
222     Result := (n > buscarN);
223   end;
224   mfilMenorIN: begin  //igual a número
225     if not TryStrToFloat(grilla.Cells[campoAfiltrar, f], n) then exit(false);
226     Result := (n <= buscarN);
227   end;
228   mfilMayorIN: begin  //igual a número
229     if not TryStrToFloat(grilla.Cells[campoAfiltrar, f], n) then exit(false);
230     Result := (n >= buscarN);
231   end;
232   end;
233 end;
234 procedure TfraFiltCampo.GridKeyPress(var Key: char);
235 {Debe ser llamado en el evento OnKeyPress, de la grilla, si se desee que el Frame tome el
236  control de este evento, iniciando el filtrado con la tecla pulsada.}
237 begin
238   if Key = #13 then exit;   //este código, no debe ser considerado como tecla de edición
239   Edit1.SetFocus;
240   Edit1.Text := Key;  //sobreescribe lo que hubiera
241   Edit1.SelStart:=length(Edit1.Text);
242 end;
243 procedure TfraFiltCampo.PreparaFiltro;
244 {Prepara }
245 const
246   MAX_PAL_BUS = 40;  //tamaño máximo de las palabras de búsqueda, cuando hay más de una
247 var
248   p: Integer;
249   buscar, numStr: String;
250 begin
251   ActualizarVisibilidadBotones;
252   if (ComboBox2.ItemIndex = -1) or (grilla = nil) or (txtBusq='') then begin
253     modFiltro := mfilNone;  //otra manera de decir que no hay filtro
254   end else if txtBusq[1] in ['=','>','<'] then begin
255     //Búsqueda por número
256     if length(txtBusq)<2 then begin  //muy pocos caracteres
257       modFiltro := mfilNone;
258       exit;
259     end;
260     campoAfiltrar := filtros[ComboBox2.ItemIndex].campo;
261     //Identifica operador y número
262     case txtBusq[1] of
263     '=': begin
264       modFiltro := mfilIgualN;  //otra manera de decir que no hay filtro
265       numStr := copy(txtBusq, 2, length(txtBusq));
266     end;
267     '<': begin
268       if txtBusq[2] = '>' then begin
269         numStr := copy(txtBusq, 3, length(txtBusq));
270         modFiltro := mfilDiferN;
271       end else if txtBusq[2] = '=' then begin
272         numStr := copy(txtBusq, 3, length(txtBusq));
273         modFiltro := mfilMenorIN;
274       end else begin
275         numStr := copy(txtBusq, 2, length(txtBusq));
276         modFiltro := mfilMenorN;
277       end;
278     end;
279     '>': begin
280       if txtBusq[2] = '=' then begin
281         numStr := copy(txtBusq, 3, length(txtBusq));
282         modFiltro := mfilMayorIN;
283       end else begin
284         numStr := copy(txtBusq, 2, length(txtBusq));
285         modFiltro := mfilMayorN;
286       end;
287     end;
288     end;
289     //Lee número a comparar
290     if not TryStrToFloat(numStr, buscarN) then begin
291       modFiltro := mfilNone;
292       exit;
293     end;
294   end else begin
295     buscar := PreparaCad(txtBusq);  //simplifica
296     campoAfiltrar := filtros[ComboBox2.ItemIndex].campo;
297     p := pos(' ', buscar);
298     if p = 0 then begin
299       //solo hay una palabra de búsqueda
300       modFiltro := mfil1Pal;  //otra manera de decir que no hay filtro
301       buscar1 := buscar;
302     end else begin
303       //Hay dos o mas palabras de búsqueda
304       modFiltro := mfil2Pal;  //otra manera de decir que no hay filtro
305       buscar1 := copy(buscar,1,p-1);
306       while buscar[p]= ' ' do
307         inc(p);  //Salta espacios. No debería terminar en espacio, porque ya se le aplicó trim()
308       buscar2 := copy(buscar, p, MAX_PAL_BUS);  //solo puede leer hasta 40 caracteres
309     end;
310   end;
311 end;
312 procedure TfraFiltCampo.ComboBox2Change(Sender: TObject);
313 begin
314   PreparaFiltro;
315   if OnCambiaFiltro<>nil then OnCambiaFiltro;
316 end;
317 procedure TfraFiltCampo.Edit1Change(Sender: TObject);
318 begin
319   if proteger then exit;   //para no actualizar
320   txtBusq := Edit1.Text;
321   PreparaFiltro;
322   if OnCambiaFiltro<>nil then OnCambiaFiltro;
323 end;
324 procedure TfraFiltCampo.Edit1Enter(Sender: TObject);
325 begin
326   //if (txtBusq='') and (Edit1.Text=msjeBuscar) then begin
327     ModoConTexto(txtBusq);
328   //end;
329 end;
330 procedure TfraFiltCampo.Edit1Exit(Sender: TObject);
331 begin
332   if txtBusq = '' then begin    //No tiene contenido
333     ModoSinTexto;
334   end;
335   ActualizarVisibilidadBotones;
336 end;
337 procedure TfraFiltCampo.Edit1KeyDown(Sender: TObject; var Key: Word;
338   Shift: TShiftState);
339 begin
340   //Pasa el evento
341   if OnKeyDown<>nil then OnKeyDown(Sender, Key, Shift);
342 end;
343 procedure TfraFiltCampo.FrameResize(Sender: TObject);
344 begin
345     Panel1.Height:=Edit1.Height;
346     Panel1.Left:=0;
347     Panel1.Top :=0;
348     Panel1.Width:=self.Width - ComboBox2.Width;
349     ComboBox2.Top:=0;
350     ComboBox2.Left := self.Width - ComboBox2.Width;
351     //Edit1.Align:=alClient;
352     Edit1.Left := 0;  //a la izquierda, dentro del panel
353     Edit1.Width := Panel1.Width - btnClose.Width;  {Se supone que solo habrá un botón
354                                                     visible, así que esto debe funcionar}
355     Edit1.Top:=2;
356 end;
357 procedure TfraFiltCampo.LeerCamposDeGrilla(cols: TGrillaDBCol_list; indCampoDef: integer);
358 {Configura todos los campos definidos, menos el 0, como campos para la búsqueda. Solo
359 se puede aplicar cuando se ha definido un objeto TfraUtilsGrilla, mediante UsarFrameUtils()
360 }
361 var
362   c: Integer;
363 begin
364   //Agrega filtro de todos los campos, menos el primero
365   for c:=1 to cols.Count-1 do begin
366     AgregarColumnaFiltro('Por ' + cols[c].nomCampo, c);
367   end;
368   ComboBox2.ItemIndex:=indCampoDef;
369 end;
370 procedure TfraFiltCampo.fraFiltCampoCambiaFiltro;
371 begin
372   if griFiltrar<>nil then griFiltrar.Filtrar;
373 end;
374 procedure TfraFiltCampo.Activar(txtIni: string);
375 {Activa el panel de búsqueda, dándole el enfoque, y poniendo un texto debúsqueda inicial. }
376 begin
377     Edit1.Text:=txtIni;
378   if Edit1.Visible then Edit1.SetFocus;
379   Edit1.SelStart:=2;
380 end;
381 procedure TfraFiltCampo.SetFocus;
382 begin
383 //  inherited SetFocus;
384   try
385     edit1.SetFocus;
386   except
387   end;
388 end;
TfraFiltCampo.Focusednull389 function TfraFiltCampo.Focused: Boolean;
390 begin
391   Result := inherited Focused;
392   if Edit1.Focused then Result := true;
393 end;
394 procedure TfraFiltCampo.Inic(grilla0: TStringGrid);
395 {Prepara al frame para iniciar su trabajo. Notar que para evitar conflictos, se ha
396 definido que no se intercepten los eventos de la grilla, en este Frame.}
397 begin
398   grilla := grilla0;
399   ComboBox2.Clear;
400   filtros.Clear;
401 end;
402 procedure TfraFiltCampo.Inic(gri: TUtilGrilla; campoDef: integer = -1);
403 {Asocia al frame para trabajar con un objeto TUtilGrilla. "campoDef" es el campo por
404 defecto que se usará cuando se muestre el filtro.}
405 begin
406   Inic(gri.grilla);
407   if campoDef>=0 then begin
408     LeerCamposDeGrilla(gri.cols, campoDef);
409   end;
410   gri.AgregarFiltro(@Filtro);  //agrega su filtro
411   {Crea un manejador de evento temporal, para ejecutar el filtro. Este evento puede
412   luego reasignarse, de acuerdo a necesida, ya que no es vital. }
413   griFiltrar := gri;
414   OnCambiaFiltro:=@fraFiltCampoCambiaFiltro;
415 end;
416 constructor TfraFiltCampo.Create(TheOwner: TComponent);
417 begin
418   inherited Create(TheOwner);
419   OnResize:=@FrameResize;
420   filtros := TFiltroGrilla_list.Create(true);
421   msjeBuscar := 'Texto a buscar';  //Inicia mensaje
422   ActualizarVisibilidadBotones;
423   ModoSinTexto;
424 end;
425 destructor TfraFiltCampo.Destroy;
426 begin
427   filtros.Destroy;
428   inherited Destroy;
429 end;
430 
431 end.
432 
433