Mostrando entradas con la etiqueta TImageList. Mostrar todas las entradas
Mostrando entradas con la etiqueta TImageList. Mostrar todas las entradas

jueves, 20 de febrero de 2020

TBitButton: cambiar imagen.

Se trata de cambiar la imagen de un botón TBitButton en tiempo de ejecución (o mediante código). En este caso las imágenes las obtenemos de TImageList y, como ejemplo, algo básico como un botón para ocultar o mostrar.


Al ejecutar el programa se tiene que ver algo así, un TMemo, que será el elemento del formulario a mostrar y ocultar, y un TBitButton que cambiará la imagen y el texto cada vez que se presione.

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, Buttons, StdCtrls;

type

{ TForm1 }

TForm1 = class(TForm)
  BitBtn1: TBitBtn;
  ImageList1: TImageList;
  Memo1: TMemo;
  procedure BitBtn1Click(Sender: TObject);
  procedure FormCreate(Sender: TObject);
private
  FOcultar:Boolean;
end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  FOcultar:=True;
  ImageList1.GetBitmap(0,BitBtn1.Glyph);
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
begin
  if FOcultar then
  begin
    ImageList1.GetBitmap(1,BitBtn1.Glyph);
    BitBtn1.Caption:='Mostrar';
    Memo1.Visible:=False;
  end
 else
 begin
   ImageList1.GetBitmap(0,BitBtn1.Glyph);
   BitBtn1.Caption:='Ocultar';
   Memo1.Visible:=True;
  end;
  FOcultar:=not(FOcultar);
end;

end.


En la variable FOcultar almacenamos el estado del botón.
En el evento FormCreate le asignamos el valor True a FOCultar y le asignamos la imagen correspondiente, en este caso la misma es la primera de TImageList que por cierto, también debemos incluir en el Form.
ImageList1.GetBitmap(0,BitBtn1.Glyph);
Para asignar la primera imagen de la lista, utilizamos el procedimiento GetBitmap de TImageList: el primer parámetro corresponde al índice de la imagen, el segundo, a la imagen destino, en este caso BitBtn1.Glyph.
Porque TBitBtn.Glyph:TBitmap = class(TFPImageBitmap).
El método (procedimiento) GetBitmap pertenece a la clase TCustomImageList:

TCustomImageList.GetBitmap(Index: Integer; Image: TCustomBitmap);

En este ejemplo también cambiamos el texto del botón.
Finalmente cambiamos el valor de FOcultar, sino, no pasa nada.

Si bien es algo sencillo, dejo el código fuente del proyecto: descargar.

domingo, 8 de diciembre de 2019

TlistView con columnas e imágenes.

TListView puede contener varias columnas, las mismas se puede definir cómodamente desde el inspector de objetos "Columns" donde solo se especifica el nombre. Luego hay que valerse de items y subitems para cargar los datos en las respectivas columnas. Todo es texto (strings) tanto el nombre de las columnas, como el items y los subitems. Para agregar un ícono usamos una lista de imágenes TImageList y la asociamos a SmallImages en TListView. También el estilo (ViewStyle) de la lista debe ser vsReport.


Agregar columnas, como vemos, es simple.

En este ejemplo, la lista debe verse así, en la primer columna en nombre del archivo, la segunda el tamaño y la tercera la fecha y hora.

Hay que ajustar un poco la lista (TListView) desde el inspector de objetos:
TListView
AutoSort:=True
ColumnClick:=True
Columns:=3 items
ReadOnly:=True
ShowColumnHeader:=True
ScrollBars:=ssAutoBoth
SmallImages:=ImageList1
SortColumn:=0
ViewStyle:=vsReport

Y también la lista de imágenes (TImageList):
TImageList
Height:=24
Width:=24

Los imágenes para la lista:

La estructura de la lista:


Si trabajamos con columnas necesitamos una lista de ítems (TListItem) con la cual agregaremos los subítems:

Por ejemplo:

var
  li:TListItem;
begin
  li:=lista.Items.Add; //Crea el ítem y lo agrega a Lista (TListView)
  li.Caption:='archivo.pas';
  li.SubItems.Add('2772');
  li.SubItems.Add('20-10-2018 15:30:58');

Como vemos no necesitamos instanciar la clase TListItem porque lo hace la función Add, su resultado es precisamente la instanciación de la variable:

function TListItems.Add: TListItem;
begin
  if Assigned(Owner) then
    Result := Owner.CreateListItem
  else
    Result := TListItem.Create(Self);
  AddItem(Result);
end;  

Siempre es bueno revisar el código fuente.

El código para llenar la lista es el siguiente:

procedure TForm1.MostrarArchivos;
var
  Arch:TSearchRec;
  li:TListItem;
  den:String='';
begin
  lista.Clear;
  opciones:=GetOpciones;
  case opciones of
    0 : if FindFirst(arbol.Path+'*',faHidden or faDirectory,Arch)<>0 then Exit;  //Si encontró devuelve 0 cero.
    1 : if FindFirst(arbol.Path+'*',faDirectory,Arch)<>0 then Exit;
    2 : if FindFirst(arbol.Path+'*',not faHidden and not faDirectory,Arch)<>0 then Exit;
    3 : if FindFirst(arbol.Path+'*',faHidden and not faDirectory,Arch)<>0 then Exit;
  end;
  repeat
    if ((Arch.Name<>'.') and (Arch.Name<>'..')) then  //Evita los directorios ocultos en Linux
    begin
      li:=lista.Items.Add;
      li.Caption:=Arch.Name;
      if (Arch.Attr and  faDirectory)<>0 then
        begin
          li.ImageIndex:=0;
          li.SubItems.Add('--');
        end
      else
        begin
          li.ImageIndex:=1;
          li.SubItems.Add(FormatFloat('0.00',Tamanio(den,Arch.Size))+' '+den);
        end;
      li.SubItems.Add(DateTimeToStr(FileDateToDateTime(Arch.Time)));
    end;
  until FindNext(Arch)<>0;
  FindClose(Arch);
  lista.Sort;
end; 

La primer columna corresponde al texto del item, en este caso, li.Caption, las restantes son subitems y el texto se asigna mediante el métodos Add.
En cuanto a la imágenes que utilizamos como iconos las cargamos en li.ImageIndex siendo 0 para carpetas y 1 para archivos.

Para obtener los datos de una lista del tipo TListView también lo hacemos mediante la clase TListItem, por ejemplo:

li:=Lista.Items[i];
ShowMessage(li.Caption);  

Descargar código fuente : TListView_Columns.7z

jueves, 15 de agosto de 2019

Cambiar imágenes en TListView y TImageList.

Lo que parecía simple, lo era, el tema fue probar varias veces mil formas hasta no conseguirlo y preguntar en el foro, donde un "agradable sujeto" me guió con este código:

ListView1.SmallImages:=nil;
ListView1.SmallImages:=ImageList1;


El problema no eran mensajes de error de ningún tipo, sino que no actualizaba la imagen que quería cambiar ya sea mediante un TImage o TImageList y simplemente, no pasaba nada.


En la primera lista, utilizando el IDE, cargo 4 ítems; la lista 2 está vacía lo mismo la lista de imágenes 2 y la imagen que se ve arriba del primer botón. La imagen corresponde al programa en ejecución luego de haber presionado los dos botones.

Con Button1, si hay un elemento de la lista1 seleccionado, se permite cambiarlo por una imagen almacenada en el disco que tenga como máximo 32x32 píxeles.

Con Button2, cargo 10 imágenes ubicadas en la misma carpeta que el programa en ImageList2 y luego se asignan a ListView1.

El problema del espacio que hay entre las imágenes, especialmente notorio en ListView1 es un problema de GTK que reserva espacio para el texto (caption) y si no se utiliza, pues lo deja igual, con otros widgets las imágenes prácticamente se tocan.

Código:

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ComCtrls,
  ExtDlgs, StdCtrls, ExtCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Image1: TImage;
    ImageList1: TImageList;
    ImageList2: TImageList;
    ListView1: TListView;
    ListView2: TListView;
    OpenPictureDialog1: TOpenPictureDialog;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
begin
  if ListView1.ItemIndex<0 then Exit;
  if OpenPictureDialog1.Execute then
    begin
      try
        Image1.Picture.LoadFromFile(OpenPictureDialog1.FileName);
      Except
         begin
           ShowMessage('Invalid image.');
           Exit;
         end;
      end;
      if (Image1.Picture.Height>32) or (Image1.Picture.Width>32) then
      begin
        ShowMessage('The image > 32x32 pix.');
        Image1.Picture.Clear;
        Exit;
      end;
    end
    else Exit;
  ImageList1.Delete(ListView1.ItemIndex);
  ImageList1.Insert(ListView1.ItemIndex,Image1.Picture.Bitmap,nil);
  ListView1.SmallImages:=nil;
  ListView1.SmallImages:=ImageList1;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  i:Word;
  imagen:TImage;
  li:TListItem;
begin
  imagen:=TImage.Create(nil);
  for i:=0 to 9 do
    begin
      imagen.Picture.LoadFromFile(Application.Location+'00'+IntToStr(i)+'.png');
      ImageList2.Insert(i,imagen.Picture.Bitmap,nil);
      li:=ListView2.Items.Add;
      li.Caption:=IntToStr(i);
      li.ImageIndex:=i;
      imagen.Picture.Clear;
    end;
  FreeAndNil(imagen);
  ListView2.SmallImages:=nil;
  ListView2.SmallImages:=ImageList2;
end;

end.

Algunos comentarios acercar del código:
Respecto de la primera lista ListView1:

  if ListView1.ItemIndex<0 then Exit;
  if OpenPictureDialog1.Execute then
    begin
      try
        Image1.Picture.LoadFromFile(OpenPictureDialog1.FileName);
      Except
         begin
           ShowMessage('Invalid image.');
           Exit;
         end;
      end;
      if (Image1.Picture.Height>32) or (Image1.Picture.Width>32) then
      begin
        ShowMessage('The image > 32x32 pix.');
        Image1.Picture.Clear;
        Exit;
      end;
    end
    else Exit;

Lo primero a validar es que se haya seleccionado un ítem de la lista, sino, chau.
Éste es un claro ejemplo de cuándo utilizar try/Except justificadamente y no sólo por el hecho de que existe, porque el usuario, todo lo puede, por ejemplo intentar abrir un archivo que no es una imagen, entonces si la imagen está OK, continuamos, caso contrario, mensaje y salimos sin romper nada.
La siguiente validación es que la imagen no exceda los 32x32 pix. que es el tamaño definido mediante el inspector de objetos para las imágenes de ListView1. Si se supera el filtro, entonces:

  ImageList1.Delete(ListView1.ItemIndex);
  ImageList1.Insert(ListView1.ItemIndex,Image1.Picture.Bitmap,nil);
  ListView1.SmallImages:=nil;
  ListView1.SmallImages:=ImageList1;

Encontré fácil 10 métodos distintos para esto, finalmente usando los métodos Delete e Insert funciona y es simple. Primero se borra el ítem según ItemIndex y luego se agrega en la posición ItemIndex la imagen de Image1.
Finalmente, la clave, se establece en nil SmallImages y luego se le asigna ImageList1.

En cuanto a la otra lista, ListView2:

procedure TForm1.Button2Click(Sender: TObject);
var
  i:Word;
  imagen:TImage;
  li:TListItem;
begin
  imagen:=TImage.Create(nil);

La variable li del tipo TListItem es necesaria para agregar elementos (ítems) a la lista vacía y no se crea, porque se crea en TListItemS, así está explicado en el código fuente por los programadores de Lazarus.

Descargar código fuente: TListView_TImageList.7z

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.