Excel 2007 gets CSV file wrong? - excel

I am using Delphi 7 to generate CSV file from data acquired from COM port. Now, this seems to work OK since any other spreadsheet software reads generated file properly (Open Office and Libre Office spreadshhet software, for instance). Excel 2007 does the following. Instead of these two column values:
1.976139e-2<TAB>22.98027
i got these two column values:
1.98E+04<TAB>2.298.027
Note that generated file opened in any text editor (Notepad++ for instance) has proper values.
What could be the problem here ?
Thanks,
Josip

If the setting in Delphi do not fit your needs you could change them before loading the CSV. But you should make sure to reset them afterwards.
var
Excel, WorkBook: Variant;
KeepDecimalSeparator, KeepThousandsSeparator: String;
KeepUseSystem: Boolean;
begin
Excel := CreateOleObject('Excel.Application');
Excel.Visible := true;
try
KeepDecimalSeparator := Excel.DecimalSeparator;
KeepThousandsSeparator := Excel.ThousandsSeparator;
KeepUseSystem := Excel.UseSystemSeparators;
Excel.DecimalSeparator := '.';
Excel.ThousandsSeparator := ',';
Excel.UseSystemSeparators := false;
WorkBook := Excel.WorkBooks.Open('C:\Temp\1.csv');
finally
Excel.DecimalSeparator := KeepDecimalSeparator;
Excel.ThousandsSeparator := KeepThousandsSeparator;
Excel.UseSystemSeparators := KeepUseSystem;
end;
end;

Try to use quotes.
"1.976139e-2"<TAB>"22.98027"

Related

Save Excel file inside Delphi service [duplicate]

I have console Delphi application which works with Excell though OLE.
The examle of code is pretty simple
procedure SaveBaseRecordsToFile(BaseName: string; PaymentRecords: TPaymentRecords);
var
i: integer;
Excel: Variant;
begin
try
Excel:=CreateOleObject('Excel.Application');
Excel.DisplayAlerts:=False;
except
on E: Exception do begin
LogWriter.WriteLog(E.ClassName + ': ' + E.Message);
exit;
end;
end;
try
Excel.Workbooks.Add;
//Excel.Worksheets.Add;
Excel.Worksheets[1].Cells[2, 1].Value := 'Account number';
Excel.Worksheets[1].Cells[2, 2].Value := 'Sum';
for i := 0 to Length(PaymentRecords) - 1 do begin
Excel.Worksheets[1].Cells[i + 3, 1].Value := PaymentRecords[i].NUMBER;
Excel.Worksheets[1].Cells[i + 3, 2].Value := PaymentRecords[i].SUMMA;
end;
Excel.ActiveSheet.Name := 'SHEET1';
Excel.Application.Workbooks[1].SaveAs(ExtractFilePath(ParamStr(0)) + BaseName + '.xls', 56);
Excel.Workbooks.Close;
Excel.Quit;
finally
Excel := Unassigned;
end;
end;
When I run this application in interactive mode (by myself) it wors perfect.
But when I try to run it through standart Microsoft Scheduler I see in log of my applications such records:
21:41:40.523: EOleSysError: Отказано в доступе, ProgID: "Excel.Application" (Access denied from Russian)
If I set in schedule task option "Run with highest privileges" I see in log of my applications such records:
20:12:04.475: EOleException: Метод SaveAs из класса Workbook завершен неверно (SaveAs method of Workbook class finished incorrectly from Russian)
Is there any way to run application with OLE call through Microsort Scheduler?
Or maybe I can work with Excel without OLE (it's pretty simple operations as you could see in my examle)? How can I do it?
The fundamental issue is that Excel needs to run in an interactive desktop. Running through the task scheduler, as you have configured it, runs it in a non-interactive desktop. That's not a supported mode of operation for Excel: http://support.microsoft.com/kb/257757
Your only real hope of success is to create the Excel file without using COM automation of Excel. There are many libraries to help you do that.

Delphi - Copying Rows in Excel

Delphi Tokyo - I have a Delphi app which is working against an Excel spreadsheet. I have a source sheet... called RawData. I need to copy specific rows (based on a filter) to another worksheet in the same workbook (called ActivitySheet). While I can get this to work... I am getting an Excel error message at the end of the process with the message 'The picture is too large and will be truncated.' This makes me believe that the command I am using is using the Clipboard, so I don't want to do that.
Here is what I am doing now...
var
ActivitySheet : _WorkSheet;
destRange : OleVariant;
begin
oExcel.Worksheets.Add(EmptyParam, EmptyParam, 1, xlWorksheet, LOCALE_USER_DEFAULT);
ActivitySheet:= oExcel.ActiveSheet as _Worksheet;
ActivitySheet.Name := 'Activity';
destRange := ActivitySheet.Range['A1', 'A1'];
// Set RawData Sheet as active
RawDataSheet.Activate(LOCALE_USER_DEFAULT);
Application.ProcessMessages;
// Now add the both filters that we want... (Not real filters, but easier to understand)
RawDataSheet.UsedRange[LOCALE_USER_DEFAULT].AutoFilter(1, '=*Blue*', xlOr, '=*Red*', True );
RawDataSheet.UsedRange[LOCALE_USER_DEFAULT].AutoFilter(2, '=Car', xlOr, '=Truck', True );
ShowMessage(IntToStr(RawDataSheet.UsedRange[LOCALE_USER_DEFAULT].Rows.Count));
RawDataSheet.UsedRange[LOCALE_USER_DEFAULT].Copy(destRange);
...
While this code works, and only copies the rows that are valid based on the current filter, it gives me the error at the 'Save Spreadsheet' stage later in my code. I thought about using a Vararray, but the ShowMessage line shows me the TOTAL rows in RawSheet, not the Filtered rows...so I would have to iterate through all the rows in the sheet (as opposed to just filtered rows) and then determine if the row is valid...
What is the best way to copy a set of filtered rows to a new sheet?
When I first saw this q, I had no idea how to do what you are asking. After a number
of false starts, I came across this answer
Excel Filtering and Copying in VBA
which shows that it can be done very simply, if you know the exact "magic spell" to do it.
Here is the code I wrote. I have used late binding to Excel throughout because it
avoids having to spray references to LOCALE_USER_DEFAULT everywhere.
type
TDefaultForm = class(TForm)
[...]
public
vExcel,
vWB,
vRange,
vSheet,
vActivitySheet,
vRawDataSheet : OleVariant;
end;
[...]
procedure TDefaultForm.CopyFilteredRange;
var
i : Integer;
vRange : OleVariant;
vDestRange : OleVariant;
begin
vRange := vRawDataSheet.Range['A1', 'A100'];
vDestRange := vActivitySheet.Range['A1', 'A1'];
vRange.SpecialCells(xlCellTypeVisible).Copy(vDestRange);
end;
procedure TDefaultForm.TestFilter;
var
vRange : OleVariant;
begin
vExcel := CreateOleObject('Excel.Application');
vExcel.Visible := True;
vWB := vExcel.WorkBooks.Add;
vRawDataSheet := vWB.ActiveSheet;
vActivitySheet := vWB.Sheets[2];
vRange := vRawDataSheet.Range['A1', 'A100'];
vRange.Item[1, 1] := 'Vehicle';
vRange.Item[2, 1] := 'Car';
vRange.Item[3, 1] := 'Truck';
vRange.Item[4, 1] := 'Truck';
vRange.Item[5, 1] := 'Car';
vRange.Item[6, 1] := 'Truck';
vRange.Item[7, 1] := 'Truck';
vRawDataSheet.UsedRange.Select;
vRawDataSheet.UsedRange.AutoFilter(Field := 1, Criteria1 := 'Car');
CopyFilteredRange;
end;
Btw, in case you are not familiar with it, the way of passing the arguments in the call
vRawDataSheet.UsedRange.AutoFilter(Field := 1, Criteria1 := 'Car');
is a special Delphi syntax for late-binding which avoids the rigmarole of passing
all the arguments required for early binding.
Also btw, I got the "Picture too large ..." message, but it seems easy to avoid: just include ClipBrd in the Uses list, and call ClipBoard.Clear after copying the filtered rows. Ordinarily, I don't like messing with the Clipboard because it's a system-wide resource, but as a fast fix here, clearing it may be acceptable.

Inserting images to an Excel sheet in Delphi

I have a question here, I have an application and I need to add images to it. I have tried:
Sheet.Shapes.AddPicture(G_V.Prog_Dir+'pic.BMP',false,true, 190, 10+(15*rowcount), 100, 100 );
it works just fine, but I don't want to give parameters, I want to insert pictures to specified (and parametric) cells because I need to add picture to the last column of the page; this excel needs to be printed I must mention that. So I tried:
Sheet.Range['E'+inttostr(rowcount),'E'+inttostr(rowcount)].Select;
Sheet.Pictures.Insert(G_V.Prog_Dir+'pic.BMP');
It looks OK at first sight, however I think this code links images to the sheet. For example, I send the created Excel to another computer and these images cannot be seen (I don't recall the exact error) and when I searched it, I found out that receiving computer needs to have images at the exact path. As a solution to this, "Sheet.Shapes.AddPicture" recommended but
as I stated before, I need another solution here.
I didn't see anybody experiencing this kind of problem, I hope someone helps me out.
You can use this code to import picture. You do not need exact path if you use AddPicture function with this parameters. it copies photo and paste it to excel.
Set p = ActiveSheet.Shapes.AddPicture(FileName:=PictureFileName,_
linktofile:=msoFalse, savewithdocument:=msoCTrue, _
left:=TargetCell.Left, Top:=TargetCell.Top, Width:=-1,_
Height:=TargetCell.Height)
The simple solution is to add the picture to the clipboard and then paste it into the sheet.
implementation
uses
VCL.Clipbrd, VCL.Graphics, Excel2000;
procedure TForm1.Test;
var
Bitmap: TBitmap;
Excel: TExcelApplication;
Worksheet: TExcelWorksheet;
begin
Bitmap:= TBitmap.Create;
try
Bitmap.LoadFromFile('c:\test.bmp');
Clipboard.Assign(Bitmap);
Excel:= TExcelApplication.Create(self);
Worksheet:= Excel.Worksheets[0];
Worksheet.Range['a1','a1'].Select;
Worksheet.Paste;
finally
Bitmap.Free;
end;
end;
This destroys the previous contents of the clipboard, which is bad form.
You can save/restore the clipboard data using the following routines (based on: https://www.devexpress.com/Support/Center/Question/Details/Q93874)
procedure TForm1.RestoreClipboardData(SourceDataStream: TMemoryStream);
var
AData: THandle;
ADataPtr: Pointer;
begin
if SourceDataStream.Size = 0 then Exit;
Clipboard.Open;
try
AData := GlobalAlloc(GMEM_MOVEABLE + GMEM_DDESHARE, SourceDataStream.Size);
try
ADataPtr := GlobalLock(AData);
try
SourceDataStream.Position := 0;
SourceDataStream.ReadBuffer(ADataPtr^, SourceDataStream.Size);
SetClipboardData(CF_BITMAP, AData);
finally
GlobalUnlock(AData);
end;
except
GlobalFree(AData);
raise;
end;
finally
Clipboard.Close;
end;
end;
procedure TForm1.SaveClipboardData(DestDataStream: TMemoryStream);
var
AData: THandle;
ADataPtr: Pointer;
begin
if DestDataStream = nil then raise Exception.Create('Dest is nil');
Clipboard.Open;
try
AData := GetClipboardData(CF_BITMAP);
if AData = 0 then Exit;
ADataPtr := GlobalLock(AData);
try
DestDataStream.Size := GlobalSize(AData);
DestDataStream.Position := 0;
DestDataStream.Write(ADataPtr^, DestDataStream.Size);
finally
GlobalUnlock(AData);
end;
finally
Clipboard.Close;
end;
end;

create and edit password protected excel file

i am using delphi7 and i want to create password protected excel file in my program and also want to edit it by unprotect it using code only.
my program requirement is that i have to log data in excel file but it will not be edited by end user.So how can i do it using delphi7?
var
xls, xlw: Variant;
begin
{load MS Excel}
xls := CreateOLEObject('Excel.Application');
{open your xls-file}
xlw := xls.WorkBooks.Open(FileName := 'd:\book1.xls', Password := 'qq',
ReadOnly := True);
{save with other file name}
xlw.SaveAs(FileName := 'd:\book2.xls', Password := 'password');
{unload MS Excel}
xlw := UnAssigned;
xls := UnAssigned;
end;
i tried but got Program in which program only save password protected file and after creating password protected excel file i do not able to edit same file again.
So please help me.

How to have responsive UI (Form) when performing long-running export task?

Good day people. First off, I'm not an native English speaker I might have some grammar mistakes or such.
I need an advice from people who has done something or an application alike mine, well, the thing is that I'm using a TProgressBar in my delphi form, another component called "TExcelApplication" and a TDBGrid.
When I export the DBGrid's content, the application "freezes", so I basically put that ProgressBar for the user to see how much the process is completed. I've realized that when the TDBGrid is retrieving and exporting each row to the new Excel workbook, you can't move the actual form, so you have to wait until the process is completed to move that form.
So, is it possible to do something (I thought about threads but I'm not sure if they could help) so the user could move the window if he wanted?
Thank you so much for taking your time in reading and giving me an advice. I'm using Delphi XE.
Here's the code I use to export the rows:
with ZQDetalles do
begin
First;
while not EOF do
begin
i := i + 1;
workSheet.Cells.Item[i,2] := DBGridDetalles.Fields[0].AsString;
workSheet.Cells.Item[i,3] := DBGridDetalles.Fields[1].AsString;
workSheet.Cells.Item[i,4] := DBGridDetalles.Fields[2].AsString;
workSheet.Cells.Item[i,5] := DBGridDetalles.Fields[3].AsString;
workSheet.Cells.Item[i,6] := DBGridDetalles.Fields[4].AsString;
workSheet.Cells.Item[i,7] := DBGridDetalles.Fields[5].AsString;
workSheet.Cells.Item[i,8] := DBGridDetalles.Fields[6].AsString;
workSheet.Cells.Item[i,9] := DBGridDetalles.Fields[7].AsString;
Next;
barraProgreso.StepIt;
end;
end;
If you want to see the whole code for the "Export" button, then feel free to see this link: http://pastebin.com/FFWAPdey
Whenever you're doing stuff that takes a significant amount of time in an application with GUI you want to put it in a seperate thread so the user can still operate the form. You can declare a simple thread as such:
TWorkingThread = class(TThread)
protected
procedure Execute; override;
procedure UpdateGui;
procedure TerminateNotify(Sender: TObject);
end;
procedure TWorkingThread.Execute;
begin
// do whatever you want to do
// make sure to use synchronize whenever you want to update gui:
Synchronize(UpdateGui);
end;
procedure TWorkingThread.UpdateGui;
begin
// e.g. updating the progress bar
end;
procedure TWorkingThread.TerminateNotify(Sender: TObject);
begin
// this gets executed when the work is done
// usually you want to give some kind of feedback to the user
end;
// ...
// calling the thread:
procedure TSettingsForm.Button1Click(Sender: TObject);
var WorkingThread: TWorkingThread;
begin
WorkingThread := TWorkingThread.Create(true);
WorkingThread.OnTerminate := TerminateNotify;
WorkingThread.FreeOnTerminate := true;
WorkingThread.Start;
end;
It's pretty straight forward, remember to always use Synchronize when you want to update visual elements from a thread. Usually, you also want to take care that the user can't invoke the thread again while it's still doing work as he's now able to use the GUI.
If the number of rows is small (and you know how many you'll have), you can transfer the data much more quickly (and all at once) using a variant array of variants, something like this:
var
xls, wb, Range: OLEVariant;
arrData: Variant;
RowCount, ColCount, i, j: Integer;
Bookmark: TBookmark;
begin
// Create variant array where we'll copy our data
// Note that getting RowCount can be slow on large datasets; if
// that's the case, it's better to do a separate query first to
// ask for COUNT(*) of rows matching your WHERE clause, and use
// that instead; then run the query that returns the actual rows,
// and use them in the loop itself
RowCount := DataSet1.RecordCount;
ColCount := DataSet1.FieldCount;
arrData := VarArrayCreate([1, RowCount, 1, ColCount], varVariant);
// Disconnect from visual controls
DataSet1.DisableControls;
try
// Save starting row so we can come back to it after
Bookmark := DataSet1.GetBookmark;
try
{fill array}
i := 1;
while not DataSet1.Eof do
begin
for j := 1 to ColCount do
arrData[i, j] := DataSet1.Fields[j-1, i-1].Value;
DataSet1.Next;
Inc(i);
// If we have a lot of rows, we can allow the UI to
// refresh every so often (here every 100 rows)
if (i mod 100) = 0 then
Application.ProcessMessages;
end;
finally
// Reset record pointer to start, and clean up
DataSet1.GotoBookmark;
DataSet1.FreeBookmark;
finally
// Reconnect GUI controls
DataSet1.EnableControls;
end;
// Initialize an instance of Excel - if you have one
// already, of course the next couple of lines aren't
// needed
xls := CreateOLEObject('Excel.Application');
// Create workbook - again, not needed if you have it.
// Just use ActiveWorkbook instead
wb := xls.Workbooks.Add;
// Retrieve the range where data must be placed. Again, your
// own WorkSheet and start of range instead of using 1,1 when
// needed.
Range := wb.WorkSheets[1].Range[wb.WorkSheets[1].Cells[1, 1],
wb.WorkSheets[1].Cells[RowCount, ColCount]];
// Copy data from allocated variant array to Excel in single shot
Range.Value := arrData;
// Show Excel with our data}
xls.Visible := True;
end;
It still takes the same amount of time to loop through the rows and columns of the data, but the time taken to actually transfer that data to Excel is drastically reduced, particularly if there's a good amount of data.

Resources