PDA

Ver la Versión Completa : problema al Guardar Archivo de ListView en Delphi


wolfran_hack
22-06-2015, 05:47:54
Hola a todos, estoy creando un pequeño soft que tiene una lista con varios datos de alumnos, estos datos se van modificando y creo un archivo especial ejemplo "NombredelArchivo.XXX" donde voy guardando los cambios y lo uso como base de datos temporal del programa, el problema que estoy teniendo es el siguiente:

tengo varios botones, uno para agregar un nuevo alumno, otro para editar una linea, otro para eliminar y otro para cambiar un valor y colorear una linea de la lista. Entonces utilizo el siguiente codigo para en cada cambio guardar en el fichero el registro nuevo:

procedure TForm1.SaveListViewToFile(AListView: TListView; sFileName: string);
var
idxItem, idxSub, IdxImage: Integer;
F: TFileStream;
pText: PChar;
sText: string;
W, ItemCount, SubCount: Word;
MySignature: array [0..2] of Char;
begin
// Inicio
with ListView1 do
begin
ItemCount := 0;
SubCount := 0;
//****
MySignature := 'LVF';
// ListViewFile
F := TFileStream.Create('NombredelArchivo.XXX', fmCreate or fmOpenWrite);
F.Write(MySignature, SizeOf(MySignature));

if Items.Count = 0 then
// List is empty
ItemCount := 0
else
ItemCount := Items.Count;
F.Write(ItemCount, SizeOf(ItemCount));

if Items.Count > 0 then
begin
for idxItem := 1 to ItemCount do
begin
with Items[idxItem - 1] do
begin
// Guardamos los SubItems
if SubItems.Count = 0 then
SubCount := 0
else
SubCount := Subitems.Count;
F.Write(SubCount, SizeOf(SubCount));
// Guardamos el Index
IdxImage := ImageIndex;
F.Write(IdxImage, SizeOf(IdxImage));
// Guardamos la Caption
sText := Caption;
w := Length(sText);
pText := StrAlloc(Length(sText) + 1);
StrPLCopy(pText, sText, Length(sText));
F.Write(w, SizeOf(w));
F.Write(pText^, w);
StrDispose(pText);
if SubCount > 0 then
begin
for idxSub := 0 to SubItems.Count - 1 do
begin
// Guardamos los Items y SubItems
sText := SubItems[idxSub];
w := Length(sText);
pText := StrAlloc(Length(sText) + 1);
StrPLCopy(pText, sText, Length(sText));
F.Write(w, SizeOf(w));
F.Write(pText^, w);
StrDispose(pText);
end;
end;
end;
end;
end;
F.Free;
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
// Guardamos los items
SaveListViewToFile(ListView1, 'NombredelArchivo.XXX');
end;

Con esto esta perfecto y funciona en todos los botones, ahora agregue varias opciones para cargar en la lista como, cargar archivo csv, xml, etc... código:

procedure ListViewFromCSV(
theListView: TListView;
const FileName: String);
var item: TListItem;
index,
comPos,
subIndex: Integer;
theFile: TStringList;
Line: String;
begin
theFile := TStringList.Create;
theFile.LoadFromFile(FileName);
for index := 0 to theFile.Count -1 do begin
Line := theFile[index];
item := theListView.Items.Add;
comPos := Pos(';', Line);
item.Caption := Copy(Line, 1, comPos -1);
Delete(Line, 1, comPos);
comPos := Pos(';', Line);
while comPos > 0 do begin
item.SubItems.Add(Copy(Line, 1, comPos -1));
Delete(Line, 1, comPos);
comPos := Pos(';', Line);
end;
item.SubItems.Add(Line);
end;
FreeAndNil(theFile);
end;

OpenDialog1.Title := 'Importar Archivo *.CSV';
OpenDialog1.InitialDir := GetCurrentDir;
OpenDialog1.Filter := 'CSV (Formato de texto separado por comas) (*.csv)|*.csv';
OpenDialog1.DefaultExt := 'csv';
// Limpiamos por completo la Lista.
ListView1.Clear;
// Se abre un dialogo para abrir un archivo *.CSV
if OpenDialog1.Execute then
begin
ListViewFromCSV(ListView1, OpenDialog1.FileName);


perfecto, se cargar el archivo todo sin problemas. Ahora el problema esta en que luego de este código coloque el:

SaveListViewToFile(ListView1, 'NombredelArchivo.XXX');

y no se guarda, y luego de esto los botones tampoco guardar nada en el NombredelArchivo.XXX, tampoco recibo ningún error, también probé de colocar el código directo:

procedure TForm1.CargarArchivoCSV1Click(Sender: TObject);
var
idxItem, idxSub, IdxImage: Integer;
Fe: TFileStream;
pText: PChar;
sText: string;
//sFileName: string;
W, ItemCount, SubCount: Word;
MySignature: array [0..2] of Char;
begin
OpenDialog1.Title := 'Importar Archivo *.CSV';
OpenDialog1.InitialDir := GetCurrentDir;
OpenDialog1.Filter := 'CSV (Formato de texto separado por comas) (*.csv)|*.csv';
OpenDialog1.DefaultExt := 'csv';
// Limpiamos por completo la Lista.
ListView1.Clear;
// Se abre un dialogo para abrir un archivo *.CSV
if OpenDialog1.Execute then
begin
ListViewFromCSV(ListView1, OpenDialog1.FileName);
//Initialization
with ListView1 do
begin
ItemCount := 0;
SubCount := 0;
//****
MySignature := 'LVF';
// ListViewFile
Fe := TFileStream.Create('NombredelArchivo.XXX', fmCreate or fmOpenWrite);
Fe.Write(MySignature, SizeOf(MySignature));

if Items.Count = 0 then
// List is empty
ItemCount := 0
else
ItemCount := Items.Count;
Fe.Write(ItemCount, SizeOf(ItemCount));

if Items.Count > 0 then
begin
for idxItem := 1 to ItemCount do
begin
with Items[idxItem - 1] do
begin
//Save subitems count
if SubItems.Count = 0 then
SubCount := 0
else
SubCount := Subitems.Count;
Fe.Write(SubCount, SizeOf(SubCount));
//Save ImageIndex
IdxImage := ImageIndex;
Fe.Write(IdxImage, SizeOf(IdxImage));
//Save Caption
sText := Caption;
w := Length(sText);
pText := StrAlloc(Length(sText) + 1);
StrPLCopy(pText, sText, Length(sText));
Fe.Write(w, SizeOf(w));
Fe.Write(pText^, w);
StrDispose(pText);
if SubCount > 0 then
begin
for idxSub := 0 to SubItems.Count - 1 do
begin
//Save Item's subitems
sText := SubItems[idxSub];
w := Length(sText);
pText := StrAlloc(Length(sText) + 1);
StrPLCopy(pText, sText, Length(sText));
Fe.Write(w, SizeOf(w));
Fe.Write(pText^, w);
StrDispose(pText);
end;
end;
end;
end;
end;
Fe.Free;
end;
end;
end;

luego deje todo como estaba pero sin guardar, que solo cargue el archivo y cree un FormOnCloseQuery y pense que si después de hacer varios cambios, cierro el programa y se guardaría el archivo con los datos:

procedure TForm1.FormOnCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
canClose:=False;
if Application.MessageBox('¿Desea salir?','Atención',mb_OkCancel + mb_IconQuestion)= idOk then begin
SaveListViewToFile(ListView1, 'NombredelArchivo.XXX');
canClose:=True;
end
else begin
canClose := False;
end;
end;

Pero no, tampoco se guardan, ni recibo ningún error, por ende estoy mareado y sin saber que hacer, ahora cierro el programa y lo abro nuevamente y los botones guardar la info cargada o modificada, pero si pruebo de agregar un archivo, nada. Lo que creo es lo siguiente, que por algún motivo el archivo en algún caso no se cierra al momento de cárgalo y como el programa quiere reescribir el archivo no puede porque ya esta abierto en otro proceso interno.. o algo así. Ustedes que opinan?

ecfisa
22-06-2015, 10:05:27
Hola wolfran_hack.

De este modo me funciona correctamente.

procedure SaveListView(LV: TListView; const aFileName, Signature: string);
var
i, j, aux: Integer;
Stream: TStream;
str: string;
tmp: Word;
begin
Stream := TFileStream.Create(aFileName, fmCreate or fmOpenWrite);
try
// guardar firma
aux := Length(Signature);
Stream.Write(aux, SizeOf(aux));
Stream.Write(PChar(Signature)^, aux);
// guardar nro items
tmp := LV.Items.Count;
Stream.Write(tmp, SizeOf(tmp));
if LV.Items.Count > 0 then
begin
for i := 0 to LV.Items.Count-1 do
begin
// Caption
str := LV.Items[i].Caption;
aux := Length(str);
Stream.Write(aux, SizeOf(aux));
Stream.Write(PChar(str)^, aux);
// ImageIndex
aux := LV.Items[i].ImageIndex;
Stream.Write(aux, SizeOf(aux));
// SubItems
if LV.Items[i].Subitems.Count > 0 then
begin
tmp := LV.Items[i].Subitems.Count;
Stream.Write(tmp, SizeOf(tmp));
for j := 0 to LV.Items[i].Subitems.Count - 1 do
begin
// Caption
str := LV.Items[i].SubItems[j];
aux := Length(str);
Stream.Write(aux, SizeOf(aux));
Stream.Write(PChar(str)^, aux);
// ImageIndex
aux := LV.Items[i].SubItemImages[j];
Stream.Write(aux, SizeOf(aux));
end;
end;
end;
end;
finally
Stream.Free;
end;
end;

procedure LoadListView(LV: TListView; const aFileName, Signature: string);
var
Stream: TFileStream;
i, j, aux: Integer;
ItemCount, SubCount: Word;
str : string;
it: TListItem;
begin
// verificar existencia
if not FileExists(aFileName) then
raise Exception.Create(Format('No se encuentra el archivo %s',[aFileName]));
Stream := TFileStream.Create(aFileName, fmOpenRead);
try
// leer firma
Stream.Read(aux, SizeOf(aux));
SetLength(str, aux);
Stream.Read(PChar(str)^, aux);
// verificar firma
if str <> Signature then
raise Exception.Create(Format('%s no es el archivo correcto',[aFileName]));
// leer nro items
Stream.Read(ItemCount, SizeOf(ItemCount));
LV.Items.Clear;
for i := 0 to ItemCount-1 do
begin
it:= LV.Items.Add;
// Caption
Stream.Read(aux, SizeOf(aux));
SetLength(str, aux);
Stream.Read(PChar(str)^, aux);
it.Caption:= str;
// ImageIndex
Stream.Read(aux, SizeOf(aux));
it.ImageIndex := aux;
// SubItems
Stream.Read(SubCount, SizeOf(SubCount));
if SubCount > 0 then
begin
for j := 0 to SubCount-1 do
begin
// Caption
Stream.Read(aux, SizeOf(aux));
SetLength(str, aux);
Stream.Read(PChar(str)^, aux);
it.SubItems.Add(str);
// ImageIndex
Stream.Read(aux, SizeOf(aux));
it.SubItemImages[j]:= aux;
end;
end;
end;
finally
Stream.Free;
end;
end;


Ejemplo de uso:

// Guardar
procedure TForm1.btnSaveClick(Sender: TObject);
const
MSG = '¿ Desea sobreescribir el archivo %s ?';
begin
with SaveDialog1 do
begin
Title := 'Guarda su ListView como archivo CSV';
InitialDir := GetCurrentDir;
Filter := 'CSV file|*.CSV';
DefaultExt := 'CSV';
FilterIndex := 1;
if Execute then
begin
if FileExists(FileName) then
if MessageDlg(Format(MSG,[FileName]), mtConfirmation,[mbYes,mbNo],0) = mrNo then
Abort;
SaveListView(ListView1, FileName, ListView1.Name);
ShowMessage('Guardado con éxito');
end;
end;
end;

// Cargar
procedure TForm1.btnLoadClick(Sender: TObject);
begin
with OpenDialog1 do
begin
Initialdir := GetCurrentDir;
Options := [ofFileMustExist];
Filter := 'CSV file|*.CSV';
DefaultExt := 'CSV';
FilterIndex := 1;
if Execute then
LoadListView(ListView1, FileName, ListView1.Name);
end;
end;


Saludos :)

wolfran_hack
22-06-2015, 16:39:52
al cargar me da:

nombre.csv no es el archivo correcto

y estoy cargando un archivo csv creado con excel ejemplo:

nombre;apellido;curso;dato1;dato2;dato3;etc
nombre;apellido;curso;dato1;dato2;dato3;etc
nombre;apellido;curso;dato1;dato2;dato3;etc
nombre;apellido;curso;dato1;dato2;dato3;etc

ecfisa
22-06-2015, 18:55:03
Hola.

Revisa la firma que le estas enviando como argumento al parámetro Signature, en el ejemplo le envío el nombre del control (vg. 'ListView1').
Pero tenes que adaptarlo a tu caso, que por lo que ví en tu códig es 'LVF' y está definido en la línea:
MySignature := 'LVF';

Otro detalle: Si intentas cargar un archivo que hayas guardado con tu código anterior, debes cambiar el órden en que guardas las propiedades de los Items. En tu código se guarda primero ImageIndex y luego Caption y en el ejemplo que te puse es al revés.

Saludos :)

ecfisa
22-06-2015, 20:23:45
Hola de nuevo.

Una cosa que no mencionaste en tu primer mensaje y que olvidé comentar en mi último: No uso Excel y desconozco si usa alguna firma (signature) y si la usa cual es.

El código de ejemplo que te puse, guarda y recupera los items y subitems de un TListView correctamente, pero no sé si funcionará del mismo modo con un archivo generado por Excel...

Saludos :)

wolfran_hack
22-06-2015, 20:35:21
Perdón me confundí con el que es para cargar archivo csv, entonces por eso me daba error, pero tampoco funciona, puse en el OnCloseQuery:

procedure TForm1.FormOnCloseQuery(Sender: TObject; var CanClose: Boolean);
begin

canClose:=False;
if Application.MessageBox('¿Desea salir?','Atención',mb_OkCancel + mb_IconQuestion)= idOk then begin
SaveListView(ListView1, 'archivo.database', ListView1.Name);
canClose:=True;
end
else begin
canClose := False;
end;
end;

y en el dialogo al cargar el csv:

if OpenDialog1.Execute then
begin
ListViewFromCSV(ListView1, OpenDialog1.FileName);
// Se Actualiza la Barra de Estado con la cantidad de Números cargados.
StatusBar1.Panels[1].Text := Format('Cliente(s): %d ', [ListView1.Items.Count]);
// Se crea un registro en el Log sobre el tema.
Memo3.Lines.Add( formatdatetime('dd/mm/yy | hh:nn:ss',now) + ' ; ' + ' Se cargo el archivo: ' + OpenDialog1.FileName + '.')
end;
SaveListView(ListView1, 'archivo.database', ListView1.Name);

y no guarda nada ni crea el archivo.

wolfran_hack
22-06-2015, 20:45:06
ahora probe de agregar unos datos y cerrar y se guardar el archivo con tu código, por ende lo que creo es que el error debe estar entre:

(* this procedure loads the content of a CSV file *)
(* to a TListView *)
procedure ListViewFromCSV(
theListView: TListView;
const FileName: String);
var item: TListItem;
index,
comPos,
subIndex: Integer;
theFile: TStringList;
Line: String;
begin
theFile := TStringList.Create;
theFile.LoadFromFile(FileName);
for index := 0 to theFile.Count -1 do begin
Line := theFile[index];
item := theListView.Items.Add;
comPos := Pos(';', Line);
item.Caption := Copy(Line, 1, comPos -1);
Delete(Line, 1, comPos);

comPos := Pos(';', Line);

while comPos > 0 do begin
item.SubItems.Add(Copy(Line, 1, comPos -1));
Delete(Line, 1, comPos);
comPos := Pos(';', Line);
end;

item.SubItems.Add(Line);
end;

FreeAndNil(theFile);
end;

y

procedure TForm1.CargarArchivoCSV1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
ListViewFromCSV(ListView1, OpenDialog1.FileName);
end;
SaveListView(ListView1, 'archivo.database', ListView1.Name);
end;

ecfisa
22-06-2015, 21:38:01
Hola wolfran_hack.

Como te dije no uso Excel, pero supongo que debe ser similar a Calc de LibreOffice, así que proba si de este modo te funciona:

procedure LoadFromCSV(LV: TListView; const aFileName: string);
var
LI : TListItem;
TS1, TS2: TStrings;
i, j: Integer;
begin
TS1 := TStringList.Create;
TS2 := TStringList.Create;
try
TS1.LoadFromFile(aFileName);
// Columnas
TS2.CommaText := TS1[0];
for i := 0 to TS2.Count-1 do
begin
LV.Columns.Add;
LV.Columns.Items[i].Caption := TS2[i];
LV.Columns[i].Width:= 100; // esta línea es opcional
end;
// Items, Subitems
for i := 1 to TS1.Count-1 do
begin
TS2.Clear;
TS2.CommaText := TS1[i];
LI := LV.Items.Add;
LI.Caption := TS2[0];
for j:= 1 to TS2.Count - 1 do
LI.SubItems.Add(TS2[j]);
end;
finally
TS1.Free;
TS2.Free;
end;
end;


Ejemplo de uso:

LoadFromCSV(ListView1, ExtractFilePath(Application.ExeName)+'Archivo.csv');

El procedimiento carga un archivo .csv a un TListView en blanco.

Por si pudiera serte de ayuda, te adjunto la prueba con el archivito .csv que generé desde el Calc.

Saludos :)

wolfran_hack
22-06-2015, 23:42:50
Gracias ecfisa, probé con tu condigo y si lo guarda, por ende algo en las 2000 lineas de mi programa esta impidiendo que se guarde el archivo, correctamente, revisare todo y comento.

wolfran_hack
23-06-2015, 03:05:45
Encontré el error, pero no lo solucione todavía, el problema esta en el OpenDialog, como no funcionaba con el CSV busque para hacerlo con un archivo XML, funciona perfecto lo carga, todo ahora, no se guardaba, y de la nada encuentro un archivo "NombredelArchivo.XXX" que yo queria crear para usarlo de archivo temporal, se creo en el mismo directorio que donde tenia el XML, se esta quedando el directorio del Opendialog con información, que por este motivo cuando hago algún cambio si se guarda si no utilizo un OpenDialog.

wolfran_hack
23-06-2015, 03:24:11
Ahora digo, creo un botón al cual le pongo:

SaveListViewToFile(ListView1, ExtractFilePath( Application.ExeName ) + 'NombredelArchivo.XXX');
MessageDlg(ExtractFilePath( Application.ExeName ),mtInformation,[mbOK], 0);

entonces, cargo el archivo XML, todo perfecto, entonces digo ya cargue el XML y ahora si apretó el botón, tendría que escribir o reemplazar o crear el archivo NombredelArchivo.XXX en el directorio del EXE, pero no lo hace, por mas que le indico que tiene que utilizar el ExtractFilePath( Application.ExeName ), el archivo se crear en el directorio donde esta el XML.

wolfran_hack
23-06-2015, 03:27:41
Solucionado:

procedure SaveListViewToFile(AListView: TListView; sFileName: string);
var
idxItem, idxSub, IdxImage: Integer;
F: TFileStream;
pText: PChar;
sText: string;
W, ItemCount, SubCount: Word;
MySignature: array [0..2] of Char;
begin
// Inicio
with AListView do
begin
ItemCount := 0;
SubCount := 0;
//****
MySignature := 'LVF';
// ListViewFile
F := TFileStream.Create( ExtractFilePath( Application.ExeName ) + 'NombredelArchivo.XXX', fmCreate or fmOpenWrite);
F.Write(MySignature, SizeOf(MySignature));

if Items.Count = 0 then
// List is empty
ItemCount := 0
else
ItemCount := Items.Count;
F.Write(ItemCount, SizeOf(ItemCount));

if Items.Count > 0 then
begin
for idxItem := 1 to ItemCount do
begin
with Items[idxItem - 1] do
begin
// Guardamos los SubItems
if SubItems.Count = 0 then
SubCount := 0
else
SubCount := Subitems.Count;
F.Write(SubCount, SizeOf(SubCount));
// Guardamos el Index
IdxImage := ImageIndex;
F.Write(IdxImage, SizeOf(IdxImage));
// Guardamos la Caption
sText := Caption;
w := Length(sText);
pText := StrAlloc(Length(sText) + 1);
StrPLCopy(pText, sText, Length(sText));
F.Write(w, SizeOf(w));
F.Write(pText^, w);
StrDispose(pText);
if SubCount > 0 then
begin
for idxSub := 0 to SubItems.Count - 1 do
begin
// Guardamos los Items y SubItems
sText := SubItems[idxSub];
w := Length(sText);
pText := StrAlloc(Length(sText) + 1);
StrPLCopy(pText, sText, Length(sText));
F.Write(w, SizeOf(w));
F.Write(pText^, w);
StrDispose(pText);
end;
end;
end;
end;
end;
F.Free;
end;
end;