In Excel OLE automation I used a worksheet's Shapes.AddPicture method to add a picture and a worksheet's Shapes.AddShape method to add a shape of a certain type (e.g. msoShapeTrapezoid).
How do I do these things in FlexCel?
Pictures can be added with AddImage, and as always APIMate will show you how.
I add an image from file. In Excel Automation I can pass -1 for width and height to the AddPicture method, since width and height is unknown without inspecting the file further. In FlexCel, I have to determine width and height by loading the file into a TGraphic first. Could FlexCel support -1 for width and height, too, if the image is read from a file?
Futhermore, it seems that AddImage ignores the LockAspectRatio property of the TImageProperties object passed to AddImage. That is, my image is stretched according to the cell range of the anchor even if LockAspectRatio is true.
Hi,
img := TUIImage.FromFile('test.png');
try
xls.AddImage(2, 3, img);
finally
img.Free;
end;
function GetImageProperties(const xls: TExcelFile; const row, col: integer; const filename: string): IImageProperties;
var
imgHeight: Int32;
imgWidth: Int32;
Img: TUIImage;
begin
Img := TUIImage.FromFile(filename);
try
imgHeight := Trunc(((Img.HeightInPoints * 96.0) / 72.0));
imgWidth := Trunc(((Img.WidthInPoints * 96.0) / 72.0));
Result := TImageProperties_Create(
TClientAnchor.Create(TFlxAnchorType.MoveAndResize, row, 0, col, 0, imgHeight, imgWidth, xls),
'');
finally
Img.Free;
end;
end;
First, thank you for the code determining width and height of an image.
Now to the LockAspectRatio issue: From another post I found that I can change the properties of the added Image using the SetObjectProperty method.Which shape options do I have to use to set the width and height of the image? Or, how do I determine the width and height of a cell or cell range so that I can use then as width or height for the added image?
The width and height of the image are not part of the ShapeOptions, they are the ShapeAnchor. To change them you would use SetObjectAnchor instead. But there is no need to change it, since the AddImage() calls that take a IImageProperties parameter allow you to specify the anchor when adding the image (IImageProperties has an Anchor property).
TClientAnchor.Create(TFlxAnchorType.MoveAndResize, row, 0, col, 0, imgHeight, imgWidth, xls)
function CalcImgSize(const xls: TExcelFile; const row1, col1, col2: integer; const filename: string): IImageProperties;
var
imgHeight: Int32;
imgWidth: Int32;
Img: TUIImage;
CorrectedImgWidth, CorrectedImgHeight: Int32;
hCells, wCells: double;
Anchor, FinalAnchor: TClientAnchor;
begin
Anchor := TClientAnchor.Create(TFlxAnchorType.MoveAndDontResize, 1, 0, col1, 0, 2, 0, col2, 0);
Anchor.CalcImageCoords(hCells, wCells, xls);
Img := TUIImage.FromFile(filename);
try
imgHeight := Trunc(((Img.HeightInPoints * 96.0) / 72.0));
imgWidth := Trunc(((Img.WidthInPoints * 96.0) / 72.0));
CorrectedImgWidth := Round(wCells);
CorrectedImgHeight := Round(imgHeight * wCells / ImgWidth);
FinalAnchor := TClientAnchor.Create(TFlxAnchorType.MoveAndResize, row1, 0, col1, 0, CorrectedImgHeight, CorrectedImgWidth, xls);
Result := TImageProperties_Create(
TClientAnchor.Create(TFlxAnchorType.MoveAndResize, row1, 0, col1, 0, FinalAnchor.Row2, FinalAnchor.Dy2, col2, 0),
'');
finally
Img.Free;
end;
end;
xls.AddImage(filename, CalcImgSize(xls, 2, 3, 7, filename));
Ok, thank you very much.
Adrian,
When I try the code you posted, it works well as long as I don't change the Default RowHeight property of the excel sheet. The picture is show distorted (width is correct, but height is not proportional to width any more)
Same problem when I change the font and size of a column (the row height then gets adjusted as well so the problem seems to be related to the previous one).
Is there nay solution?
Are you changing the row height before or after adding the picture?
In the code above we used TFlxAnchorType.MoveAndResize which as it name implies, resizes the image when you resize the rows (making them look distorted). Same happens in Excel if you add an image, set its properties to Move and Resize, and then resize the rows.
Can you try with TFlxAnchorType.MoveAndDontResize instead and let me know if the problem persists?
I change the row height before adding the picture.
Changing the anchortype to MoveAndDontResize does not solve the problem.
Changing the rowsize after adding the picture yields the same result (with both anchortypes).
Changing the rowsize afterwards in Excel itself back to the default value resizes the distorted picture to its correct dimensions. Apparently the picture is added with anchor values calculated from the default row height and not from the actual row height at the time of insertion?
If you are changing the default row size, are you setting:
xls.DefaultRowHeightAutomatic := false;
after changing it?
I've tried it here and I can't really make it produce distorted images.
My example app is here:
http://www.tmssoftware.biz/flexcel/samples/image_aspect_ratio.zip
I even try changing the row after the image is inserted, and as it is MoveAndDontResize, it doesn't resize. (under the hood FlexCel is calculating the image size before resizing the row, then resizing the row, then recalculating the new ending row with the row size changed)
Note also that in the code above there were 2 MoveAndDontResize which had to be changed to MoveAndResize.
Do you think you can modify the example app I attached to show what you are seeing?
Thanks for the sample project. I searched a while comparing it to my own project and found out when the problem arises. I changed your code to the following code :
// Added from
if FileExists('....\testresult.xlsx') then begin
xls := TxlsFile.Create(True);
xls.Open('....\testresult.xlsx');
end else begin
// Added to
xls := TXlsFile.Create(1, TExcelFileFormat.v2016, true);
xls.NewFile;
end;
try
// Added from
for i := xls.SheetCount downto 1 do begin
if (CompareText(xls.GetSheetName(i),'Report') = 0) then begin
xls.ActiveSheet := i;
xls.DeleteSheet(1);
Break; // Exit for loop
end;
end; // for i := 0 to xls.SheetCount-1...
xls.InsertAndCopySheets(0,1,1,xls); // Create 1 new sheet and insert at the front
xls.ActiveSheet := 1;
xls.SheetName := 'Report';
// Added to
xls.AddImage(filename, CalcImgSize(xls, 2, 3, 7, filename));
xls.Save('....\testresult.xlsx');
finally
xls.Free;
end;
The code to resize the rows was removed, it has nothing to do with the problem.
I added code (between "add from" and "add to") to open an existing excel file, and when the file already contains a sheet "Report", it is first removed, a new one is inserted and renamed to "Report" so we have a clean sheet.
If you run the program a second time (so that testresult.xlsx already exists and has a sheet "Report") then the image that is added is distorted. If I remove the line that inserts a new sheet, then there is no problem any more.
I also remarked that on inserting the new sheet, not only the image is distorted but also the global column width of the sheet is different from the default width of the other sheets (which is not the case testresult.xlsx is newly created). There is however no code that changes the column width of the sheet.
Inserting a new sheet in an existing file is the cause of the distortion. Can you explain this behaviour? Am I perhaps inserting sheets the wrong way?
Hi,
TXlsFile.Create(1, TExcelFileFormat.v2016, true);
xls := TXlsFile.Create(1, TExcelFileFormat.v2016, true);
xls.NewFile;
xls := TXlsFile.Create(1, TExcelFileFormat.v2016, true);
xls := TXlsFile.Create(true);
xls.NewFile(1, TExcelFileFormat.v2016) ;
xls := TXlsFile.Create(true);
xls.NewFile(1, TExcelFileFormat.v2016) ; //creates a file in excel 2016 format.
xls.NewFile(1) //same as xls.NewFile(1, TExcelFileFormat.v2003) ; creates a file in Excel 2003 format.
Weird that you don't see any aspect ratio distortion. I my case, if no xls file exists, the picture is inserted with dimensions 7,51x8,47cm (aspect ration ok). If I rerun the program while the xls file exists, the picture is inserted with dimensions 5,16x6,77cm (should be 6,02x6,77cm). I also noticed that in the first case the font in the file is arial 10 (excel 2003 default) and the column width is 10,71. In the second case, the font is still arial 10 but the column width is 8,43. I noticed that the latter is the default column width when the font is calibri 11. Somehow a mix with these values occurs which causes the distortion.
I could solve the problem however using your hint. Using the same file format for opening/creating excel files solves the problem. Is there any way however to get to know the excelfileformat of the file you are opening or is it sufficient to test for the extension : .xls is v2003, .xlsx is >= v2007 (to open with which file format then?)
I encountered still a last peculiarity. If you set the row height to a different default value, I encountered again a (little) distortion of the inserted pictured with some row height values and no distortion with other row height values. It turns out that setting the row height results in a small calculation where numbers are truncated. F.i.
xls.DefaultRowHeight := Round(1120); // Results in a row height of 10.50 (14 pixels)
xls.DefaultRowHeight := Round(1220); // Results in a row height of 12 (16 pixels)
xls.DefaultRowHeight := Round(13*20); // Results in a row height of 12.75 (17 pixels)
What happens afaik is that the values you try to set (11-12-13) are multiplied by 4/3 (=96/72) and then get truncated to get a integer value in pixels.
The calculation of the the image positions (dx1,dx2,dy1,dy2 : see code) however is done using the original row height you tried to set (11-12-13) instead of the real row height (10.50-12-12.75). This causes distortion for setting the row height to 11 and 13 but not for setting it to 12. In this case you better use 11.25 (instead of 11) and 13.5 (instead of 13).
I just wanted this to share with others since it caused me some trouble to find out what was wrong.
Thanks a lot for your support. It is fast and very helpful!
xls.GetDefaultFormatNormalStyle