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

jueves, 11 de marzo de 2021

La función Assigned

Assigned es un función sencilla y muy útil, devuelve True si el parámetro pasado no es Nil y False si es Nil, pero cuidado que las variables del tipo puntero no reciben el valor nil por el solo hecho de declararlas, es decir por default o de manera predeterminada; así, en el siguiente ejemplo, Assigned devolverá true:

var
  aPtrChar:PChar;
begin
  if Assigned(aPtrChar) then
    ShowMessage('aPtrChar no es nil.')
  else
    ShowMessage('aPtrChar es nil');
end;

se mostrará el mensaje de que no es nil, aunque no apunte a ningún lado. Por eso es muy recomendable utilizar esta función en lugar de preguntar si es distinto de nil: 

if aPtrChar <> nil.

Algunos ejemplo de su uso:

Si por ejemplo en un árbol queremos mostrar información sobre un nodo con el evento Click, primero hay que asegurarse de que en efecto hay un nodo seleccionado para que no ocurran desastres.

procedure TForm1.arbolClick(Sender: TObject);
begin
  if not Assigned(arbol.Selected) then Exit;
  Label1.Caption:=arbol.Path;
  MostrarArchivos;
end;

Continuando con ejemplos TTreeView:

nuevamente se verifica que el nodo esté asignado, caso contrario, se sale sin hacer nada. En este caso también se averigua si el nodo es raíz.

procedure TForm1.tvChange(Sender: TObject; Node: TTreeNode);
begin
  if (not(Assigned(Node)) or (Node.Level<1)) then Exit;
  edNombre.Text:=tv.Selected.Text;
  edDocumento.Text:=PRegistro(tv.Selected.Data)^.documento;
  edNacionalidad.Text:=PRegistro(tv.Selected.Data)^.nacionalidad;
  edEstadoCivil.Text:=PRegistro(tv.Selected.Data)^.estadocivil;
end;

Un caso con TStringList:

procedure TFFiltrar.FormCreate(Sender: TObject);
begin
  if not(Assigned(filtros)) then filtros:=TStringList.Create;
  Memo1.Lines:=filtros;
end; 

la variable filtros está declarada en otra unidad y puede ser que ya haya sido creada (inicializada o instanceada) por eso, ante de intentar crearla dos veces, lo que generará un hermoso run time error, se verifica mediante Assigned.

Para finalizar, un ejemplo de TListView:

procedure TForm1.BQuitarClick(Sender: TObject);
begin
  if ((LView.Items.Count<1) or (not Assigned(LView.Selected))) then Exit;
  ListaCarpetas.Borrar(LView.Items[LView.ItemIndex].Caption);
  ListaCarpetas.ToListView(LView);
end;

se usa Assigned sobre TListView.Selected, de esta forma si no hay ningún elemento seleccionado, no se hace nada.

lunes, 9 de diciembre de 2019

TListView: evitar ítems duplicados.

El componente TListView carece de una propiedad para impedir ítems duplicados, seguramente porque a su vez, los ítems son una clase que además posee subítems.
Una forma para evitar esta situación, sería, antes de agregar el ítem, recorrer la lista y comparar el texto (Caption) del ítem con los ya existentes, una función con un ciclo for to alcanza y es una manera correcta, pero si la lista tiene 15.000 ítems ¿que pasa?, para empezar, una lista del tipo TListView no debería contener tal cantidad y para terminar, también.
Pero TListView tiene un método (función) que hereda de TCustomListView: FindCaption.

{------------------------------------------------------------------------------}
{   TListItems FindCaption                                                     }
{------------------------------------------------------------------------------}
function TListItems.FindCaption(StartIndex: Integer; Value: string;
  Partial, Inclusive, Wrap: Boolean; PartStart: Boolean): TListItem;
var
  I: Integer;
  CaptionFound, AllChecked: Boolean;
begin
  result := nil;
  if (Count = 0) or (StartIndex >= Count) or (not Inclusive and (count = 1)) then Exit;
  CaptionFound := False;
  AllChecked := False;
  if Inclusive then
    I := StartIndex
  else begin
    I := succ(StartIndex);
    if I >= Count then I := 0;
  end;
  if Wrap then Wrap := (StartIndex <> 0);
  repeat
    if Partial then begin
      if PartStart then
        CaptionFound := pos(Value, Item[I].Caption) = 1
      else
        CaptionFound := pos(Value, Item[I].Caption) <> 0;
    end else
      CaptionFound := Value = Item[I].Caption;
    if not CaptionFound then begin
      Inc(I);
      if Wrap then begin
        if I = Count then
          I := 0
        else
          if I = StartIndex then
            AllChecked := True;
      end else begin
        if I = Count then AllChecked := True;
      end;
    end;
  until CaptionFound or AllChecked;
  if CaptionFound then result := Item[I];
end; 

Las ventajas son las opciones y que ya está hecha.

¿Cómo utilizarla?

if Assigned(ListView1.FindCaption(0,'eltexto',False,True,False,True)) then Exit;

Este código debe ejecutarse antes de agregar un ítem, si el ítem ya existe, entonces FindCaption devuelve un ítem ("completo"), caso contrario, devuelve nil.

Para probarlo basta con este ejemplo:

unit principal;

{$mode objfpc}{$H+}

interface

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

type

  { TForm1 }

  TForm1 = class(TForm)
    BAgregar: TBitBtn;
    lPath: TLabel;
    Lista: TListView;
    Panel1: TPanel;
    arbol: TShellTreeView;
    Splitter1: TSplitter;
    procedure arbolClick(Sender: TObject);
    procedure BAgregarClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  lPath.Caption:=arbol.Path;
end;

procedure TForm1.BAgregarClick(Sender: TObject);
var
  li:TListItem;
begin
  if not Assigned(arbol.Selected) then Exit;
  if Assigned(Lista.FindCaption(0,arbol.Path,False,True,False,True)) then Exit;
  li:=Lista.Items.Add;
  li.Caption:=arbol.Path;
end;

procedure TForm1.arbolClick(Sender: TObject);
begin
  if not Assigned(arbol.Selected) then Exit;
  lPath.Caption:=arbol.Path;
end;

end. 

Este programa agrega los nombres de las carpetas mostradas en un TShellTreeView a un TListView nombrada "lista" cada vez que se presiona el botón agregar, evitando duplicados.

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