Im working in a Tdbadvgrid from TMS. I am adding colums dynamically based on a sql query length. Everything works great, with the exception of the column width saving.
Adding the columns:
//Captions
for i := 0 to oRow.Count - 1 do
begin
grdFieldData.Columns.Insert(1);
grdFieldData.Cells[i + 1, 0] := TabelList.Captions[i].Caption;
end;
//Data
for r := 0 to TabelList.Count - 1 do //rows
begin
for c := 0 to oRow.Count - 1 do //cols
begin
grdFieldData.Cells[c+1, r+1] := TabelList.Rows[r].Fields[c].Value;
end;
if r <> TabelList.Count - 1 then
grdFieldData.RowCount := grdFieldData.RowCount + 1;
end;
Now the save function is built into the TMS TDBAdvGrid, and looks like this:
I have tried messing with all the options with no luck.
The table has 1 fixed row (for captions) and one empty row. The row is only there because the number of fixed rows must be smaller then the number of rows.
When saving the data to either .ini file or to registry, it saves and loads the first column, but the dynamicly added ones get written in as default value (64) but never saved/loaded when i drag them to adjust size. The .ini file table looks like this:
[Recept]
Col0=20
Col1=97
Col2=64
Col3=64
Col4=64
Col5=64
Col6=64
Col7=64
Col8=64
Col9=64
Col10=64
Col11=64
Col12=64
Col13=64
Col14=64
When loaded in it looks like this:
Does anyone know what I can do to make the columns save properly so the widths will be saved?
So, after a few hours of sitting with it, I decided that there was no reason to struggle so much with something built in when I could just build something simple myself.
In this instance, it simply saves to a chosen ini file at a chosen directory. The filename and directory are currently hardcoded to each inhreited class. A feature to change them could easily be implemented. Perhaps even a directory selecter to a button or something like it.
The code for loading (called on FormShow):
procedure TfrmReceptEditor.LoadColWidths;
var
Ini: TIniFile;
i: Integer;
path: String;
filename: String;
begin
inherited;
path := 'C:\';
filename := 'grid.ini';
Ini := TIniFile.Create(path + filename);
try
for i := 0 to grdFieldData.ColCount - 1 do
begin
grdFieldData.Columns.Items[i].Width := Ini.ReadInteger('Recept','col'+IntToStr(i),75);
end;
grdFieldData.FixedColWidth := 20;
finally
Ini.Free;
end
end;
And the code for saving the data (on FormClose):
procedure TfrmReceptEditor.SaveColWidths;
var
Ini: TIniFile;
i: Integer;
path: String;
filename: String;
begin
inherited;
path := 'C:\';
filename := 'grid.ini';
Ini := TIniFile.Create(path + filename);
try
for i := 0 to grdFieldData.ColCount - 1 do
begin
Ini.WriteInteger('Recept', 'col'+IntToStr(i), grdFieldData.Columns.Items[i].Width);
end;
finally
Ini.Free;
end;
end;
Some might want to build in features to check if the Ini files exists (aka if the entered path is correct). But it will work smoothly even if the file doesnt exist, it will simply create it.
Related
I imported many Excel worksheets to many tFDMemTables one by one and modified them. Now I try to save them to any kind of ONE file to maintain with tFDMemTable further, no more Excel. How can I make many tFDMemTables to ONE object to save to ONE file, not by APPEND?
I use Delphi 10.3 Community in Windows 10. The reference recommends FireDAC that I use it.
In .Net I make multi table layers into ONE DataSet and ONE XML file with the following simple code. But in Delphi it seems that a data table means a data set. So what is that holds many data tables like .Net DataSet?
DataSet.Tables.Add(Table);
DataSet.WriteXml(FileName);
The code below shows how to save a series of Excel files (workbooks) into rows
of an FDMemTable on the basis of one workbook per FDMemTable row. This is done
by saving the Excel file in a blob field of the FDMemTable.
The GetFiles method shows how to scan a folder for Excel files and save them to
the FDMemTable using the SaveFile method. The FDMemTable row includes the name
of the Excel file and the path to where it was found.
When the GetExcelMethod completes, it saves the FDMemTable contents to a file
which can then be copied elsewhere.
The WriteFiles method reads the Excel files from the FDMemTable and writes them
to a specified directory: this method also shows how to open the file using Excel.
Of course, the techniques shown here are not restricted to Excel files: by
adjusting the file mask in the GetFiles, it could find and save files of any type.
uses [...] ShellAPI;
type
TForm1 = class(TForm)
[...]
private
ID : Integer; // used to generate iD field for FDMemTable1
[...]
public
end;
procedure TForm1.GetFiles(Path : String);
// Find all files in a given directory and save them to FDMemTable1
var
SearchRec : TSearchRec;
Res : Integer;
FN : String;
begin
Path := Path + '\*.xl*';
Res := FindFirst(Path, faAnyFile, SearchRec);
if Res = 0 {SearchRec.Attr and faAnyFile = faAnyFile} then begin
repeat
SaveFile(ExtractFilePath(Path) + SearchRec.Name);
Res := FindNext(SearchRec);
until Res <> 0;
FindClose(SearchRec);
FN := ExtractFilePath(Application.ExeName) + 'Excelfiles.XML';
FDMemTable1.SaveToFile(FN, sfXML);
end;
end;
procedure TForm1.SaveFile(FileName : String);
// Save an individual file to FDMemTable1
var
APath,
AName : String;
begin
APath := ExtractFilePath(FileName);
AName := ExtractFileName(FileName);
inc(ID);
FDMemTable1.Insert;
FDMemTable1.FieldByName('ID').AsInteger := ID;
FDMemTable1.FieldByName('FilePath').AsString := APath;
FDMemTable1.FieldByName('FileName').AsString := AName;
TBlobField(FDMemTable1.FieldByName('FileData')).LoadFromFile(FileName);
FDMemTable1.Post;
end;
procedure TForm1.WriteFiles;
// Extract files from FDMemTable1 to s given directory
var
FileName : String;
begin
if not FDMemTable1.Active then
FDMemTable1.Open;
FDMemTable1.First;
while not FDMemTable1.Eof do begin
FileName := FDMemTable1.FieldByName('FileName').AsString;
TBlobField(FDMemTable1.FieldByName('FileData')).SaveToFile('C:\Temp\'+ FileName);
// Uncomment the following line to have the file opened in Excel; you'll need to add ShellAPI to your Uses list
// ShellExecute(Handle, 'Open', PChar('C:\Temp\' + Filename), '','',SW_SHOWNORMAL);
FDMemTable1.Next;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
FieldDef : TFieldDef;
begin
ID := 0;
FieldDef := FDMemTable1.FieldDefs.AddFieldDef;
FieldDef.Name := 'ID';
FieldDef.DataType := ftInteger;
FieldDef := FDMemTable1.FieldDefs.AddFieldDef;
FieldDef.Name := 'FilePath';
FieldDef.DataType := ftString;
FieldDef.Size := Max_Path;
FieldDef := FDMemTable1.FieldDefs.AddFieldDef;
FieldDef.Name := 'FileName';
FieldDef.DataType := ftString;
FieldDef.Size := Max_Path;
FieldDef := FDMemTable1.FieldDefs.AddFieldDef;
FieldDef.Name := 'FileData';
FieldDef.DataType := ftBlob;
FDMemTable1.CreateDataSet;
end;
procedure TForm1.btnSaveClick(Sender: TObject);
begin
GetFiles('D:\aaad7\aaaofficeauto');
end;
procedure TForm1.btnWriteFilesClick(Sender: TObject);
begin
WriteFiles;
end;
I don't know another way. Append all data into a new dataset and export it using SaveToFile() procedure.
I have a solution for you.
Use TFDDataSet variable to store all data from Excel files.
DataSet: TFDDataSet
Each time you get a data table, merge it into the DataSet via TFDDataSet.MergeDataSet method.
// Getting data from Excel files.
// Suppose there are 10 excel files.
for I := 0 to 9 do
begin
// Stored Excel data to TempDataSet.
// ...
// Merge TempDataSet to DataSet.
DataSet.MergeDataSet(TempDataSet, dmDataAppend, mmAdd);
end;
Finally, use TFDDataSet.SaveToFile method to write XML file.
// Write to XML file.
DataSet.SaveToFile('C:\Data\DataSetData.xml', sfXML);
I have a total of 3214 .doc. I need to open the first file, copy its contents, paste it in a RichEdit, extract some text, insert it into a database then move on to the next file and repeat the procedure.
So far I've managed to:
Open the 1st .doc/any 1 .doc only
Copy the content and paste it in the RichEdit
Extract the text from the RichEdit
Insert the extracted text into the database
Close the opened .doc and clear the content of RichEdit
I've loaded all 3214 filenames, in order, into a Memo.
Once I finish with the 1st file from the list, how do I now make it move to the next .doc from the list and do the same thing, repeating this till I finish all the 3214 .doc files? Currently reading about loops but I can't figure it out yet.
Code so far:
procedure TForm1.Button4Click(Sender: TObject);
var
content: string;
StartPos: Integer;
endPos: Integer;
i: integer;
fname: string;
WordApp : Variant;
begin
WordApp := CreateOleObject('Word.Application');
for i := 1 to 1 do
fname := Memo1.Lines[i - 1];
WordApp.Visible := True;
WordApp.Documents.Open('C:\Users\tcsh\Desktop\all\'+fname);
WordApp.ActiveDocument.Select;
WordApp.Selection.Copy;
RichEdit1.Lines.Add(WordApp.Selection);
WordApp.documents.item(1).Close;
WordApp.Quit;
content:= RichEdit1.Text;
//<text extract code is here>
begin
//<sql code is here>
end;
RichEdit1.Clear;
Edit1.Clear;
Edit2.Clear;
Edit3.Clear;
Edit4.Clear;
Edit5.Clear;
Edit7.Clear;
Edit8.Clear;
//the TEdit's hold the extracted text so the sql can retrieve it from them and insert into the database
end;
for i := 1 to 1 do
Hmmm, that will only run once..
You may also want to try:
Create the WordApp object in each iteration ,
Add a time delay in between each iteration (using Sleep and Application.ProcessMessages) (as LU RD points out this is not necessary)
Code sample below:
for i := 0 to Memo1.Lines.Count - 1 do
begin
WordApp := CreateOleObject('Word.Application');
fname := Memo1.Lines[i];
WordApp.Visible := True;
WordApp.Documents.Open(fname);
WordApp.ActiveDocument.Select;
WordApp.Selection.Copy;
Memo2.Lines.Add(WordApp.Selection);
Memo2.Lines.Add('===');
WordApp.documents.item(1).Close;
WordApp.Quit;
//Sleep(1000); -> not needed
//Application.ProcessMessages;
end;
Try it with System.IOUtils.TDirectory.GetFiles
GetFiles('C:\temp\', '*.doc');
Here is an example
You'll find a few here on SO.
Update
...
var
line: string;
...
for line in Memo1.Lines do begin
<your code per file>
ShowMessage(line);
end
I need to parse every single file in a directory including the files in sub directories and sub sub directories and ...
I have already successfully done this using the code below :
class function TATDFTPUtility.findAllDirectoryFiles(var ftpClient: TIdFTP; directory: String; deepness: Integer = 0): TidFTPListItems;
var
I: Integer;
localDirectoryListing: TIdFTPListItems;
baseDirectory: string;
begin
Result := TIdFTPListItems.Create;
*// this function uses ftpClient.ChangeDirUp until it reaches the '' directory*
changeUpToDirectory(ftpClient, '');
try
ftpClient.ChangeDir(directory);
ftpClient.List;
Result.Assign(ftpClient.DirectoryListing);
localDirectoryListing := Result;
baseDirectory := ftpClient.RetrieveCurrentDir;
for I := 0 to localDirectoryListing.Count - 1 do
begin
if (localDirectoryListing.Items[i].ItemType = ditDirectory) then
begin
result := addTwoFTPListItems(result, findAllDirectoryFiles(ftpClient, baseDirectory + '/' + localDirectoryListing.Items[i].FileName));
end;
end;
except
end;
end;
class function TATDFTPUtility.addTwoFTPListItems(listA: TIdFTPListItems; listB: TIdFTPListItems): TidFTPListItems;
var
i: integer;
begin
Result := listA;
for I := 0 to listB.Count - 1 do
begin
with Result.Add do
begin
Data := listB.Items[i].data;
Size := listB.Items[i].Size;
ModifiedDate := listB.Items[i].ModifiedDate;
LocalFileName := listB.Items[i].LocalFileName;
FileName := listB.Items[i].FileName;
ItemType := listB.Items[i].ItemType;
SizeAvail := listB.Items[i].SizeAvail;
ModifiedAvail := listB.Items[i].ModifiedAvail;
PermissionDisplay := listB.Items[i].PermissionDisplay;
end;
end;
end;
Now the problem is that this takes about 15-20 minutes !!!
Is there a more efficient way ?
Here is a few fact about this particular case :
1- After i ran the program it found about 12000 files with almost 100-200 directories but the highest deepness was about 7
2- I only need to parse and i do not need to download or upload anything
3- The reason i have used an exception is because inside the FTP there are a few folder which i do not have access and this causes an access violation Error in IdFTP and i used try...except to ignore any directory which can not be accessed.
You are calling ChangeDirUp() (potentially many times?) and then calling ChangeDir() afterwards. If directory is an absolute path, you can just call ChangeDir() one time to jump directly to the target folder and avoid ChangeDirUp() altogether. The recursive loop inside of findAllDirectoryFiles() is using absolute paths from RetrieveCurrentDir(), so the repeated calls to ChangeDirUp() and ChangeDir() are wasted overhead. You can greatly reduce overhead by not navigating up and down the folder tree unnecessarily.
findAllDirectoryFiles() is returning a newly allocated TIdFTPListItems that the caller must free. That in itself is generally a bad design choice, but especially in this case because the recursive loop is not freeing those secondary TIdFTPListItems objects at all, so they are being leaked.
When adding files to the output TIdFTPListItems, you are only adding their filenames and not their paths. What good is recursively searching for files if the caller does not know where each file was found? Or do you only care about the filenames and not the paths?
You are ignoring the deepness parameter completely.
With that said, try something more like this instead:
class procedure TATDFTPUtility.findAllDirectoryFiles(ftpClient: TIdFTP; const directory: String;var files: TIdFTPListItems; deepness: Integer = -1);
var
I: Integer;
baseDirectory: string;
subDirectories: TStringList;
item: TIdFTPListItem;
localDirectoryListing: TIdFTPListItems;
begin
try
if directory <> '' then
ftpClient.ChangeDir(directory);
ftpClient.List;
except
Exit;
end;
baseDirectory := ftpClient.RetrieveCurrentDir;
localDirectoryListing := ftpClient.DirectoryListing;
subDirectories := nil;
try
for I := 0 to localDirectoryListing.Count - 1 do
begin
case localDirectoryListing[i].ItemType of
ditFile: begin
item := files.Add;
item.Assign(localDirectoryListing[i]);
// if you need the full path of each file...
item.FileName := baseDirectory + '/' + item.FileName;
end;
ditDirectory: begin
item := localDirectoryListing[i];
if ((item.FileName <> '.') and (item.FileName <> '..')) and
((deepness = -1) or (deepness > 0)) then
begin
if subDirectories = nil then
subDirectories := TStringList.Create;
subDirectories.Add(baseDirectory + '/' + item.FileName);
end;
end;
end;
end;
if subDirectories <> nil then
begin
if (deepness > 0) then Dec(deepness);
for I := 0 to subDirectories.Count - 1 do begin
findAllDirectoryFiles(ftpClient, subDirectories[I], files, deepness);
end;
end;
finally
subDirectories.Free;
end;
end;
When calling findAllDirectoryFiles() for the first time, you can set directory to either:
a blank string to start searching in the current directory.
a subfolder that is relative to the current directory.
an absolute folder that is relative to the server's root.
And set deepness to either
-1 for endless recursion
>= 0 to specify how deep to recurse.
files := TIdFTPListItems.Create;
try
TATDFTPUtility.findAllDirectoryFiles(ftpClient, 'desired directory', files, desired deepness);
// use files as needed...
finally
files.Free;
end;
Is there a function to get the last created folder from a given path?
I want to see the last created folder in order to check if my camera has taken photos today.
Another approach I was thinking was to get the system date and then start searching for a folder that contained the current date.However if the camera date is wrong then this approach wont work!
Thanks. Any other ideas?
ex:
if lastcreatedfolder(dir_path):='05012016' then
showmessage('TODAY A FOLDER WAS CREATED')
else
showmessage('NO FOLDER WAS CREATED TODAY!');
Delphi 2010 also has the IOUtils.pas unit.
Using this unit, the last created folder may be found as follows:
uses
IOUtils, Types, DateUtils;
function FindLastCreatedDirectory(const APath: string): string;
var
LastCreateTime : TDateTime;
PathsInQuestion: TStringDynArray;
n : Integer;
begin
LastCreateTime := MinDateTime;
Result := '';
PathsInQuestion := TDirectory.GetDirectories(APath);
for n := Low(PathsInQuestion) to High(PathsInQuestion) do
begin
if CompareDateTime(TDirectory.GetCreationTime(PathsInQuestion[n]), LastCreateTime) = GreaterThanValue then
begin
LastCreateTime := TDirectory.GetCreationTime(PathsInQuestion[n]);
Result := PathsInQuestion[n];
end;
end;
end;
The last created directory in a given path can be found using the System.SysUtils.FindFirst function.
The TimeStamp field of the TSearchRec record can be checked using the function's var F parameter in order to evaluate the timestamp of a file system element.
uses
System.SysUtils,
Winapi.Windows;
function getLastCreatedDirectory(const APath: string): string;
var
res: TSearchRec;
lastCreatedFileTime: TFileTime;
begin
Result := '';
FillChar(lastCreatedFileTime, SizeOf(TFileTime), 0);
if FindFirst(APath, faDirectory, res) = 0 then begin
try
repeat
if (res.Attr and faDirectory) = 0 then
Continue;
if (res.Name = '.') or (res.Name = '..') then
Continue;
{if res.TimeStamp > lastCreatedTime then begin
lastCreatedTime := res.TimeStamp;
Result := ExtractFilePath(APath) + res.Name;
end;}
if CompareFileTime(res.FindData.ftCreationTime, lastCreatedFileTime) = 1 then begin
lastCreatedFileTime := res.FindData.ftCreationTime;
Result := ExtractFilePath(APath) + res.Name;
end;
until FindNext(res) <> 0;
finally
System.SysUtils.FindClose(res);
end;
end;
end;
begin
WriteLn(getLastCreatedDirectory('C:\Program Files (x86)\*'));
ReadLn;
end.
EDIT 2
Since res.TimeStamp seems to give the last modified date and the TSearchRec.Time field has been deprecated, the folder's creation time can be obtained evaluating the res.FindData.ftCreationTime field of the TSearchRec record.
if folder created by yourself you can save that folder name on an array even if name is random character, but if folder created by program or etc,
you can go to folder
cd "path"
exp: cd /home
and use 'bellow' command that sort by date created files and folders,
so if there is meny files use this:
ls -lt | less
also 'head' command can be usefull for you i think
I pass the parameter value '*1.dat' to FindFirst, still the first file that the FindFirst() routine return is 46checks5.dat, very consistently.
Is this a known problem?
vpath:=trim(vpath);
result:=true;
try
res:=findfirst(vpath+'\'+vmask,faarchive,search); //vmask = *1.dat
try
while res=0 do
begin
vlist.add(search.name); //searchname returned is 46checks5.dat!!!
res:=findnext(search);
end;
finally
findclose(search);
end;
except
result:=false;
end;
The reason is that the file has a "long" name, i.e. with more than 8 characters. For such files Windows also creates "short" names, that usually are created in the form longna~1.dat and this short name is found via *1.dat wildcard.
You can easily reproduce the same behaviour in command prompt in an empty directory:
C:\TEMP>echo. > 46checks5.dat
C:\TEMP>dir /x *1.dat
Volume in drive C has no label.
Volume Serial Number is 5C09-D9DE
Directory of C:\TEMP
2011.04.15 21:37 3 46CHEC~1.DAT 46checks5.dat
1 File(s) 3 bytes
The documentation for FindFirstFile(), which is the underlying API for FindFirst states:
The search includes the long and short
file names.
To workaround this issue, then, rather than using Delphi's wrapper to FindFirstFile(), call the Win32 API FindFirstFileEx(). Pass FindExInfoBasic to the fInfoLevelId parameter.
You have something else wrong.
I created a folder C:\Temp\Test, and put three files in it:
TestFile1.txt
TestFile2.txt
TestFile3.txt
I then dropped a TMemo on a new blank form in a new project, and added this code to the 'FormCreate' event:
procedure TForm1.FormCreate(Sender: TObject);
var
sPath: string;
sFile: string;
SR: TSearchRec;
begin
sPath := 'C:\Temp\Test';
sFile := '*1.txt';
Memo1.Lines.Clear;
if FindFirst(sPath + '\' + sFile, faArchive, SR) = 0 then
begin
try
repeat
Memo1.Lines.Add(SR.Name);
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;
end;
When the form was shown, the TMemo showed exactly one file, TestFile1.txt, just as I would expect.