viernes, 17 de agosto de 2018

TMemo: evitar el enter.

Si bien una de sus propiedades es WantReturns y le podemos asignar False para evitar la línea nueva, a veces no funciona, al menos con la versión 1.6 de Lazarus y 3.0 de FreePascal usando el widget Gtk-2.
No obstante la solución es muy simple utilizando el evento OnKeyPress de TMemo:

procedure TForm1.mmDetalleKeyPress(Sender: TObject; var Key: char);
begin
  if (Key in [#13,#10]) then Key:=#0;
end;


mmDetalle es la variable del tipo TMemo.
Si la tecla presionada es Enter se anula.

TEdit: rellenar con ceros u otro caracter.

La función AddChar incluida en la unidad StrUtils hace esto.

function AddChar(C: Char; const S: string; N: Integer): string; 

El primer parámetro es el caracter con el cual vamos a rellenar los espacios del string, el segundo parámetro es el string a rellenar y el tercero la cantidad de lugares a rellenar (lo normal es que sea la misma cantidad de caracteres del string).

Ejemplo: un TEdit para que el usuario ingrese el número de un comprobante y al abandonar el TEdit, el mismo complete con ceros a la izquierda. Nos valemos del método OnExit del TEdit:

procedure TForm1.edNroExit(Sender: TObject);
begin
  edNro.Text:=AddChar('0',edNro.Text,8);
end;


El edNro es la variable del tipo TEdit y la propiedad MaxLength es 8, el resto de las propiedades son las predefinidas por el IDE Lazarus.
En lugar de pasar el número 8 se podría pasar la propiedad edNro.MaxLength.

jueves, 16 de agosto de 2018

TDBLookupComboBox: Validar.

Validar un combo del tipo TDBLookupComboBox con las siguientes propiedades:

Style: cdDropDown
ArrowKeysTravers: True
AutoComplete: True

Es una configuración muy cómoda para el usuario, pero permite que deje el valor del combo en blanco o con un valor que no coincide con ningún elemento, por el motivo que sea (distracción o "a ver que pasa").
Nota: para combos con pocos elementos conviene utilizar el estilo (style) csDropDownList y deshabilitar la propiedad de autocompletado.
Volviendo al tema de inicio, la validación se realiza a través del evento OnExit:

procedure TFCRegCompras.cmbProvExit(Sender: TObject);
begin
  if cmbProv.KeyValue=Null then
  begin
    ShowMessage('Seleccione un proveedor.');
    cmbProv.SetFocus;
  end;

end;

Desde ya es muy conveniente antes de cargar y habilitar el combo, verificar que el dataset no esté vacío, si esto sucediese, el programa no se colgaría, pero una vez que el usuario entra al combo, no podría salir nunca porque KeyValue siempre sería Null y al utilizar SetFocus vuelve a entrar al combo. Es una validación muy útil pero deben tomarse ciertas precauciones. Por ejemplo, en un programa de registración de compras, donde otros combos y valores dependen del proveedor seleccionado, usando el evento OnChange del combo de proveedores, es fundamental tomar este tipo de medidas.

lunes, 13 de agosto de 2018

TDBGrid: ordenar por columnas ASC y DESC con flechas.



Anteriormente escribí acerca de ordenar las columnas de un DBGrid de forma simple y en un solo sentido. Ahora veremos como hacerlo de forma tal que además de poner en negrita el título de la columna que posee el orden, le agregue una flecha ascendente (arriba) o una descendente (abajo) y que si el usuario hace click en el título de una columna y la misma es la que está ordenando la grilla, entonces cambie el orden de la misma, es decir, si está ordenada ascendentemente pasará a ordenarse en sentido descendente.

Para las flechas necesitamos un TImageList.


Este componente se encuentra en la paleta Common Controls (el último que se ve en la imagen).


Desde el inspector de objetos estableceremos las propiedades Height (alto) y Width (ancho) de ImageList1 en 16 pixeles. Luego cargamos las imágenes de las flechas en dicho componente.

Éstas son las flechas utilizadas:


Ahora debemos conectar la lista de imágenes con la grilla.


Seleccionamos el DBGrid en el inspector de objetos y en TitleImageList asignamos ImageList1. Luego en "Eventos" creamos el procedimiento para OnTitleClick:

procedure TForm1.DBGrid1TitleClick(Column: TColumn);
var
  i:Integer;
  AscDes:String;
begin
  if Column.Title.ImageIndex=0 then AscDes:=' DESC ' else AscDes:=' ASC ';
  for i:=0 to DBGrid1.Columns.Count-1 do
  begin
    DBGrid1.Columns.Items[i].Title.Font.Style:=[];
    DBGrid1.Columns.Items[i].Title.ImageIndex:=-1;
  end;
  ZQuery1.Close;
  sqlOrder:=' ORDER BY '+Column.FieldName+AscDes+';';
  sqlgrid:=sqlSelect+sqlWhere+sqlOrder;
  ZQuery1.SQL.Text:=sqlgrid;
  ZQuery1.Open;
  Column.Title.Font.Style:=[fsBold];
  if AscDes=' DESC ' then Column.Title.ImageIndex:=1 else Column.Title.ImageIndex:=0;
end;


Las variables sqlOrder, sqlGrid, sqlSelect y sqlWhere las defino como privadas para la clase del formulario, son del tipo string y su utilización es más que obvia, permitiendo por ejemplo, ordenar manteniendo un filtro definido por el usuario (sqlWhere).
Atención a no confundir Column con Columns, la primera viene como parámetro, es la columna en la cual el usuario hizo click. En cambio Column"s" es miembro de TDBgrid.
Lo primero que hacemos es averiguar si la columna (variable Column del tipo TColumn) está ordenada de manera descendente, caso contrario la damos como ascendente y establecemos el valor correspondiente a la variable local AscDes que luego utilizaremos en la consulta SQL.
Lo siguiente es recorrer las columnas (Columns) del grid y quitarles el [bold] y la flecha.
Cerramos el Query, armamos el string de la consulta y reabrimos la misma.
Finalmente marcamos en negrita el título de la columna y se asignamos la imagen de la flecha que le corresponde.










jueves, 5 de julio de 2018

TListBox: buscar.


El siguiente código se ejecuta cuando se presiona el botón buscar, si la cadena de texto ingresada coincide con algún ítem de la lista, el mismo es seleccionado.

procedure TFCProv.BBuscarClick(Sender: TObject);
var
  nombre, item:String;
  i, cant:Integer;
begin
  nombre:=UpperCase(edBuscar.Text);
  cant:=Length(nombre);
  for i:=0 to lbProv.Count-1 do
  begin
    item:=UpperCase(lbProv.Items[i]);
    if nombre=LeftStr(item,cant) then
    begin
      lbProv.ItemIndex:=i;
      Break;
    end;
  end;
end;


En este caso la lista contiene nombres de proveedores, está ordenada, convierte a mayúsculas la cadena a buscar y el elemento a comparar. La variable entera cant se utiliza para saber la cantidad de caracteres que ingresó el usuario y hacer la búsqueda parcial, es decir, si el usuario ingresa "Ac" y hay en la lista un elemento "ACME" se compara "AC" con "AC". Como lo que se quiere es solo marcar el elemento en la lista (lbProv.ItemIndex), se hace y se utiliza break para salir.

miércoles, 4 de julio de 2018

TDBLookupComboBox validar e impedir NULL.

Lo más simple es definir la propiedad Style en csDropDownList pero no siempre es lo que necesitamos. Por ejemplo cuando son muchos los registros que se cargan en el combo, le damos al usuario la posibilidad de que escriba las primeras letras y le traiga coincidencias, esto se logra estableciendo a True la propiedad AutoComplete. La contra es que el usuario puede abandonar el combo sin ninguna coincidencia, dejando el KeyValue (que es del tipo Variant) en Null. Esto a su vez lanzará un error si la validación la hacemos con números enteros, por ejemplo if combo.KeyValue<0 then...

Opción 1: no permitir que el usuario abandone el combo sin la correcta selección de un elemento.

Definir el evento OnExit.

procedure TFCProv.cmbLocalidadExit(Sender: TObject);
begin
  if cmbLocalidad.KeyValue=Null then
  begin
    ShowMessage('Seleccione una localidad.');
    cmbLocalidad.SetFocus;
  end;
end;


Opción 2: en algún caso en que se permite no seleccionar nada en el combo y debemos guardar 0 (cero) en la base de datos.


Por ejemplo, las actividades de 2 a 5 son opcionales y al crear el Form se les estable el valor de KeyValue en 0 para que no muestre nada. (La normalización y desnormalización de bases de datos está fuera del alcance de este ejemplo.).

if cmbActividad2.KeyValue=Null then
  DMProv.ZQProv.FieldByName('actividad2').AsInteger:=0
else
  DMProv.ZQProv.FieldByName('actividad2').AsInteger:=cmbActividad2.KeyValue;


Esta validación se hace al momento de guardar el registro y en todos los combos excepto el 1, si KeyValue es Null se guarda 0, caso contrario el valor de KeyValue.

TEdit solo números.

Para que una variable del tipo TEdit acepte solo números hay que definir el evento OnKeyPress de esta forma:

procedure TFCProv.edit1KeyPress(Sender: TObject; var Key: char);
begin
if not (Key in ['0'..'9',#8, #9]) then Key:=#0;
end;


De esta forma el usuario solo podrá ingresar números y borrar.

Si bien el tipo TEdit tiene una propiedad NumbersOnly, la misma no funciona si se utiliza el widget GTK-2 en sistemas operativos GNU con kernel Linux.

#8 es para permitir borrar y #9 la tecla de tabulación, cualquier otra tecla se anula con Key:=#0.

miércoles, 20 de junio de 2018

LazReport: cambiar el valor de numeración de las hojas.

En LazReport podemos utilizar la variable propia del generador de reportes [PAGE#] para imprimir el número de página, generalmente lo hacemos en la banda pie de página. De esta forma la primera página será la número 1 y las siguientes incrementarán su valor en 1.
Si queremos que la primera página tenga otro valor, podemos definir una variable en el reporte, en este caso llamada hojanumero y en el evento GetValue pasarle el valor que deseamos. Luego sumamos esta variable a [PAGE#] y le restamos 1.

En Lazarus podemos valernos cómodamente de un TSpinEdit para que el usuario ingrese el número inicial y establecemos los valores mínimos y máximos con lo cual nos ahorramos código de validación.


El valor de SpinEdit1.Value lo pasamos a la variable del reporte hojanumero.

En el reporte, añadimos un cuadro de texto en la banda pie de página e ingresamos lo siguiente:

Hoja N°[ ( [PAGE#] + [hojanumero] -1) ]

Otra opción es restarle 1 al valor antes de enviarlo al reporte y quitar el -1 de la fórmula anterior.

LazReport EVariantError

Un error muy común: Invalid variant  type cast, esta excepción puede ser lanzada por LazReport cuando utilizamos variables en el reporte que las enviamos mediante eventos como el típico GetValue.

Resulta que si bien Free Pascal no distingue entre mayúsculas y minúsculas, el componente LazReport sí lo hace con las variables del reporte, motivo que está fuera del alcance de esta entrada. En resumen, el string que utilizamos en dicho evento o cualquier otro evento de LazReport que utilice el par  de valores parname y parvalue, en parname debemos respetar las minúsculas.

Ejemplo del error:


Motivo del error:


En LazReport la variable está escrita toda en minúscula.


Mientras que en el evento está escrita combinando mayúsculas y minúsculas.

Solución: simplemente escribir correctamente la variable definida en el reporte, es decir todo en minúsculas, en este caso se cambia 'FechaReporte' por 'fechareporte'.

domingo, 17 de junio de 2018

TDBGrid: ordenar por columnas.

Ordenar los datos de una consulta SQL mostrados en un DBGrid al hacer click en la columna. En este caso además de ordenar, pondremos en bold (negrita) el título de la columna que está ordenada. (Para algo más avanzado aquí)

En el inspector de objetos, en DBGrid, eventos, buscar: OnTitleClick


y generar el evento (procedimiento):

procedure TFdCtas.DBGridCtasTitleClick(Column: TColumn);
var
  i:Integer;
begin
  for i:=0 to DBGridCtas.Columns.Count-1 do DBGridCtas.Columns.Items[i].Title.Font.Style:=[];
  ZQCtas.Close;
  ZQCtas.SQL.Text:='SELECT * FROM dcuentas ORDER BY '+Column.FieldName+';';
  ZQCtas.Open;
  Column.Title.Font.Style:=[fsBold];
end;


En el ciclo for quitamos "Bold" de todas las columnas y al final del código lo establecemos para la actual (que viene como parámetro).

Quedando así: