I am trying to automate a series of PivotCharts within Excel. I am using Delphi Rio and Excel 2019. I have an XLSX file, with one sheet, called 'Sheet1'. I have enabled record macro within Excel, and recorded the VBA code. I have ported it to Delphi. Everything compiles, and I get an AV when I go to add my row fields (which becomes Axis fields in the PivotChart). In Excel, to record the macro, I select my sheet with my data, go to Insert/Pivot Chart, checking 'Add Data to Data Model', and then build a simple PivotChart.
The VBA code is
Cells.Select
Workbooks("TestFile.xlsx").Connections.Add2 "WorksheetConnection_Sheet1!$A:$CU" _
, "", "WORKSHEET;S:\Temp\[TestFile.xlsx]Sheet1", "Sheet1!$A:$CU", 7, True, _
False
Sheets.Add
ActiveWorkbook.PivotCaches.Create(SourceType:=xlExternal, SourceData:= _
ActiveWorkbook.Connections("WorksheetConnection_Sheet1!$A:$CU"), Version:=7). _
CreatePivotChart(ChartDestination:="Sheet3").Select
ActiveChart.ChartType = xlColumnClustered
ActiveChart.ChartStyle = 201
With ActiveChart.PivotLayout.PivotTable.CubeFields("[Range].[Fiscal Quarter]")
.Orientation = xlRowField
.Position = 1
End With
With ActiveChart.PivotLayout.PivotTable.CubeFields("[Range].[Territory Name]")
.Orientation = xlColumnField
.Position = 1
End With
ActiveChart.PivotLayout.PivotTable.CubeFields.GetMeasure "[Range].[Pipeline]", _
xlSum, "Sum of Pipeline"
ActiveChart.PivotLayout.PivotTable.AddDataField ActiveChart.PivotLayout. _
PivotTable.CubeFields("[Measures].[Sum of Pipeline]"), "Sum of Pipeline"
The problem I run into (when running my Delphi compiled executable' is when adding the 'Fiscal Quarter' rowfield. It APPEARS that CubeFields is not defined, although I get 'data inaccessible' when I mouse over it. So my first question, in Delphi, is CubeFields created automatically?
My Delphi code is
procedure BuildPivotChart;
var
myChart: _Chart;
myChartShape: Shape;
myPivotCache: PivotCache;
SourceSheet, DestSheet: _Worksheet;
myConnection: WorkbookConnection;
ConnName, ConnString, ConnCommand: String;
begin
// Create the connection since we are adding to DataModel
ConnName := 'WorksheetConnection_Sheet1!$A:$CU';
ConnString := 'WORKSHEET;S:\Temp\[TestFile.xlsx]Sheet1';
ConnCommand := 'Sheet1!$A:$CU';
myConnection := oExcel.ActiveWorkbook.Connections.Add2(ConnName, '', ConnString, ConnCommand, 7, True, False);
// Create a new Sheet, and get a handle to it...
oExcel.Worksheets.Add(EmptyParam, EmptyParam,1, xlWorksheet, LCID );
// Get a handle to the new sheet and set the Sheet Name
DestSheet := oExcel.ActiveSheet as _Worksheet;
DestSheet.Name := 'Graphs';
// Create a Pivot Cache, with a Chart object
myPivotCache := oExcel.ActiveWorkbook.PivotCaches.Create(xlExternal, myConnection, 7);
myChartShape := myPivotCache.CreatePivotChart(DestSheet, xlColumnClustered, 10, 10, 300, 300);
myChartShape.Select(EmptyParam);
myChart := oExcel.ActiveChart;
myChart.ChartStyle := 201;
if oExcel.ActiveChart.PivotLayout.PivotTable.PivotCache.IsConnected = True then
ShowMessage('Connected');
// Now add Row Fields
// ALL THREE OF THE BELOW LINES gives AV
oExcel.ActiveChart.PivotLayout.PivotTable.CubeFields.AddSet('[Range].[Fiscal Quarter]', 'MyName').Orientation := xlRowField;
oExcel.ActiveChart.PivotLayout.PivotTable.CubeFields['[Range].[Fiscal Quarter]'].Orientation := xlRowField;
// Thinking that the CUBE is not defined... check to see cube fields count..
ShowMessage(IntToStr(oExcel.ActiveChart.PivotLayout.PivotTable.CubeFields.Count));
...
When I step through my Delphi code, on the 'myPivotCache.CreatePivotChart' line, I see the chart object show up in Excel, and I see my columns listed in the Pivot selected on the right side of Excel. I have a suspicion that something is wrong with CubeFields, but I can't prove it, or I don't know how to fix it... I DO get my 'Connected' popup.
Any help or ideas appreciated.
This problem occurs when using EXCEL_TLB units.
It can be solved by changing to OLE development.
Use add unit "Excel2010" to "Uses" after interface, as:
interface
uses Excel2010
Related
I am using Delphi to write a program which builds a Pivot Table. Because I want to use Distinct Count, I have to 'Add to Data Model', which apparently creates an OLAP cube in the background. This means that I cannot use the "normal" Excel_TLB unit, but instead have to use the GetActiveOLE based routines...
I can connect, I can build the data model, I can create the pivot cache and pivot table object, but when I go to add the fields to the Pivot, I get an error at runtime, normally 'Member not Found'.
When I step through my code, after I create the PivotTable object, I can go to excel, and I see the list of possible columns for my pivot table. They are listed under a 'Range' level. (Which is what I would expect when you 'Add to Data Model' for a pivot). When I turn on record Macro, and manually add the field (category) to the filter section, the VBA code looks like this...
With ActiveSheet.PivotTables("PivotTable1").CubeFields("[Range].[Category]")
.Orientation = xlPageField
.Position = 1
End With
Below is my Delphi Code. It is the last commented lines which do NOT work.
procedure Build_Pivot;
var
// Connection Info
SheetName: String;
ConnRange, ConnName, ConnString, ConnCommand: String;
ConnPath, ConnFile : String;
ConnCount : Integer;
XLApp: Variant;
gConnection: Variant;
// Now define variables for the Pivot
myPivotCache: Variant;
myPivotTable: Variant;
StartCellRange: Variant;
fFilter1, fFilter2: Variant;
begin
XLApp := GetActiveOleObject('Excel.Application');
// Create the connection if we are adding to DataModel
// Now parse our gFileName
ConnPath := ExtractFileDir(gFileName); // Global Variable of the FileName
ConnFile := ExtractFileName(gFileName);
SheetName := 'RawData';
ConnRange := '$A:$H';
ConnCount := XLApp.ActiveWorkbook.Connections.Count;
ConnName := 'WorksheetConnection' + IntToStr(ConnCount) + '_' + SheetName + '!' + ConnRange;
ConnString := 'WORKSHEET;' + ConnPath + '[' + ConnFile + ']' + SheetName;
ConnCommand := SheetName + '!' + ConnRange;
gConnection := XLApp.ActiveWorkbook.Connections.Add2(ConnName, '', ConnString, ConnCommand, 7, True, False);
// Create the PivotCache
myPivotCache := XLApp.ActiveWorkbook.PivotCaches.Create(xlExternal, gConnection, 7);
// Create the PivotTable object;
StartCellRange := XLApp.ActiveWorkbook.ActiveSheet.Range['A5', 'A5'];
myPivotTable := myPivotCache.CreatePivotTable(StartCellRange, 'PivotTable1', True, 7);
// I can now see my Pivot Object in Excel
// Now add Filter Fields. I have tried multiple variations, but cannot get anything to work
// fFilter1 := XLApp.ActiveWorkbook.ActiveSheet.PivotTables('PivotTable1').CubeFields('[Range].[Category]');
// fFilter1 := myPivotCache.PivotTable.CubeFields('[Range].[Category]');
// fFilter1 := myPivotTable.CubeFields('[Range].[Category]');
fFilter1.Orientation := xlPageField;
Any help or ideas greatly appreciated.
Found the answer. The parameter needs to be passed as an array, and not within parenthesis. This code works...
fFilter1 := myPivotTable.CubeFields['[Range].[Category]'];
fFilter1.Orientation := xlPageField;
Delphi 10 / Seattle, with Excel 2013. I am writing a plugin (using AddIn Express) for Excel. One of the things that I need to do is create a series of Excel Pivot Tables/Pivot Charts. I have recorded macros within Excel, so I have VBA code to do what I want. My challenge is porting it to Delphi.
My code compiles, and when I step through it, the very last line gives me the error .. Method 'SetSourceData' not supported by Automation Object. FYI - XLApp is a variable point to the Excel Application.
procedure TMyTemplateForm.Pivot_TouchesByQuarter;
var
myPivotCache: OleVariant;
myActive_WB : OleVariant;
MyChart : OleVariant;
ChartSourceRange : OleVariant;
TabDestination : string;
begin
// Add the new Sheet
XLApp.Connect;
myActive_WB := XLApp.ActiveWorkbook;
XLApp.Worksheets.Add(EmptyParam, EmptyParam,1, xlWorksheet, LOCALE_USER_DEFAULT );
// Get a handle to the new sheet and set the Sheet Name
sheet_graph1 := XLApp.ActiveSheet;
sheet_graph1.Name := 'Graph1'; // CANNOT CONTAIN SPACES.. ?????
// Parameters: SourceType, SourceData, Version
// Doc at: https://msdn.microsoft.com/en-us/library/office/ff839430.aspx
myPivotCache := myActive_WB.PivotCaches.Create(xlDatabase,'Raw Data!R1C1:R1048576C36',xlPivotTableVersion15);
// Parameters: TableDestination, TableName, DefaultVersion
TabDestination := 'Graph1!R3C1';
myPivotCache.CreatePivotTable(TabDestination, 'PivotTable1',xlPivotTableVersion15);
// Select where we want this placed...
sheet_Graph1.Cells.Item[3, 1].Select;
// https://msdn.microsoft.com/en-us/library/office/jj228277.aspx
// Create the chart object
myChart := sheet_Graph1.Shapes.AddChart2(201, xlColumnClustered);
// Define the Range that is the source for the chart. This is the Pivot Table I created just above
ChartSourceRange := sheet_Graph1.Range['Graph1!$A$3:$C$20'];
// Tell the Pivot Chart to use the Chart Range
myChart.SetSourceData(ChartSourceRange);
end;
Why am I getting this error? As a related question, can I point my Chart Source to the PivotTable1 object? Right now, it is hard coded to specific cell locations, but depending on the data, my Pivot table can be bigger than from Row3 to Row20.
If it helps any, the VBA macro code (last 2 lines) is..
ActiveSheet.Shapes.AddChart2(201, xlColumnClustered).Select
ActiveChart.SetSourceData Source:=Range("Sheet1!$A$3:$C$20")
I found the answer. Instead of
// Tell the Pivot Chart to use the Chart Range
myChart.SetSourceData(ChartSourceRange)
the code should be
myChart.chart.SetSourceData(ChartSourceRange);
Delphi 10 / Seattle, Excel 2013. I am using Delphi to build an Excel plugin. This plugin will create a Pivot table. My challenge is setting the specific FUNCTION I want (Count vs Average vs Sum, etc). If I do NOT set a function, the default (COUNT) is used, and my code works fine, the pivot table is like it should be. If I try to change the function on a PivotField, then I get an error "Unable to set the Function property of the PivotField class." In searching the error, I have found 2 common causes. (1) The Pivot table has to have a default version of xlPivotTableVersion15, and (2) the PivotField orientation has to be set to xlDataField. I have done both, and when moused-over, the IDE shows the Integer values for both, so I know they are defined.
I have tried getting (as opposed to setting) the value of the PivotField.function. I get a similar error "Unable to get the Function property of the PivotField class". The column I am wanting to Sum is called '#Clicks'. The offending line of code is
myPivotTable.PivotFields('#Clicks').function := xlSum;
If I comment this line out, my routine runs fine, although I get the default COUNT function, instead of the SUM function that I want.
Any ideas appreciated. Note that when the line is commented out, and I run the code, I can then go into Excel, and go into the PivotField listings, and change the function from Count to Sum. Here is my full code.
procedure T_ZTemplateForm.TestPivotTable;
var
myPivotCache: OleVariant;
myPivotTable : OleVariant;
myActive_WorkBook : OleVariant;
TabDestination : string;
f1: OleVariant;
begin
// Add the new Sheet
XLApp.Connect; // XLApp is a global variable, pointing to the Excel instance
myActive_WorkBook := XLApp.ActiveWorkbook;
XLApp.Worksheets.Add(EmptyParam, EmptyParam,1, xlWorksheet, LOCALE_USER_DEFAULT );
// Get a handle to the new sheet and set the Sheet Name
sheet_graph1 := XLApp.ActiveSheet;
sheet_graph1.Name := 'Graph1';
// Create a Pivot Cache
myPivotCache := myActive_WorkBook.PivotCaches.Create(xlDatabase,'Raw Data!R1C1:R1048576C36',xlPivotTableVersion15);
// Create a Pivot table within the PivotCache
TabDestination := 'Graph1!R3C1';
myPivotTable := myPivotCache.CreatePivotTable(TabDestination, 'PivotTable1',xlPivotTableVersion15);
// Now start adding the fields...
f1 := myPivotTable.PivotFields('Fiscal Quarter');
f1.Orientation := xlRowField;
f1.Position := 1;
myPivotTable.PivotFields('#Clicks').orientation := xlDataField;
// myPivotTable.PivotFields('#Clicks').function := xlSum;
end;
Update I can reproduce your problems with Set_Function and GetFunction using Seattle and Excel 2007, so please disregard the original version of my answer.
However, I have found a way to use CreateDataField to create a PivotField with a Function of xlCount, and it is very simple.
Given local variables
var
DataField : OleVariant;
Value : OleVariant;
the following code executes without complaint and correctly
Value := xlCount;
DataField := DestinationSheet.PivotTables('APivotTable').AddDataField(
vPivotField,
'Count',
Value
);
whereas
DataField := DestinationSheet.PivotTables('APivotTable').AddDataField(
vPivotField,
'Count',
xlCount
);
fails with the error message you quoted. So I can only guess that when AddDataField is called with the "raw" value xlCount, the Function_ argument the compiler generates is somehow incorrectly "packaged" whereas when the argument is an OleVariant containing the xlCount value, it is correctly packaged.
I'll leave you to try out the other XlConsolidationFunction values - I've had enough of this problem for now!
Original Answer: Judging by my experiments, you can change the Excel 'function' you want to use without specifying the final parameter (in your case xlSum) of AddDataField. In fact with Seattle and Excel2007, I can't get AddDataField to execute without getting a 'The parameter is incorrect' exception for any value of the final, 'function' parameter.
I have a WorkBook with a table of Company names, dividend payment dates and amounts. The table headers are Company, PaymentDate and Amount.
The code below works for me and allows me to choose the function to be applied to the Amount column, simply by specifying the name of the function as the Caption parameter of AddDataField. I've used late binding mainly for ease of set-up, and so that I can easily omit arguments for parameters I don't want to specify.
Code:
procedure TForm1.TestPivotTable;
var
vXLApp : OleVariant;
PivotCache: OleVariant;
PivotTable : OleVariant;
ActiveWorkBook : OleVariant;
DestinationSheet : OleVariant;
FN : String;
Destination : OleVariant;
PivotField : OleVariant;
DataField : OleVariant;
begin
vXLApp := CreateOleObject('Excel.Application');
vXLApp.Visible := True;
FN := 'D:\aaad7\officeauto\MAPivot.xlsm';
Assert(FileExists(FN));
vXLApp.Workbooks.Open(FN);
ActiveWorkBook := vXLApp.ActiveWorkbook;
PivotCache := ActiveWorkbook.PivotCaches.Create(SourceType := xlDatabase,
SourceData := 'Table1', //'R2C1R30C3',
Version:=xlPivotTableVersion2000);
DestinationSheet := ActiveWorkBook.Sheets['Sheet3'];
Destination := DestinationSheet.Name + '!R3C1';
PivotTable := PivotCache.CreatePivotTable(TableDestination := Destination,
TableName := 'APivotTable'
);
DestinationSheet.Select;
DestinationSheet.Cells[3, 1].Select;
DestinationSheet.PivotTables('APivotTable').PivotFields('Company').Orientation := xlRowField;
DestinationSheet.PivotTables('APivotTable').PivotFields('Company').Position := 1;
DestinationSheet.PivotTables('APivotTable').PivotFields('PayDate').Orientation := xlRowField;
DestinationSheet.PivotTables('APivotTable').PivotFields('PayDate').Position := 2;
DestinationSheet.PivotTables('APivotTable').PivotFields('Amount').Orientation := xlRowField;
DestinationSheet.PivotTables('APivotTable').PivotFields('Amount').Position := 3;
PivotField := DestinationSheet.PivotTables('APivotTable').PivotFields('Amount');
DataField := DestinationSheet.PivotTables('APivotTable').AddDataField(
Field := PivotField,
Caption := 'Sum');
end;
This issue can be demonstrated both in Delphi driving Excel through Ole-automation, but also from a Word/VBA macro. I am showing a test Word Macro (below) to prove it is not a Delphi issue, but also adding Delphi code as this might be easier for some.
This is a big issue for us at present and I wonder if anyone else has seen/solved this, or might at least have some suggestions, since I have spent a lot of time trying various workaround and googling for solutions. We need to get the images sized correctly as we have a hard specification that the images can not have any aspect ratio changes.
The issue is as follows. If we add an image from a jpeg file on to an Excel chart using the Chart.Shapes.AddPicture() method it works nicely as long as Excel is visible. The image appears where we place it, and when you inspect the image properties the horizontal and vertical scaling are both 100%. However we are wanting to perform this procedure on a large number of files, and due to the complexity of some of the other steps, having Excel visible is not great, as there is lots of flashing, resizing etc (which does not look very professional). It also slows the process down.
Now if we perform the exact same steps with Excel hidden (as you normally would doing using COM-Automation), the image appears, but is subtly changed. The amount of change can vary depending on the state of the chart window. But typically I see a Height scaling of 107% and width scaling of 99%.
Word Macro-VBA
Sub Test_Excel()
'
' Test_Excel Macro
'
'
'You will need to go to 'Tools/References' in the Word VBA editor and enable reference to
' Microsoft Excel
Dim Oxl As New Excel.Application
Dim owB As Excel.Workbook
Dim Chrt As Excel.Chart
Dim DSht As Excel.Worksheet
Dim i As Integer
Dim Rng As Excel.Range
Dim Ax As Excel.Axis
Dim Pic As Excel.Shape
'File name of an image on disk we are going to place on the graph. we don't want
' to link to it, as the Excel file will be sent to someone else.
'For the purposes of the test this file can be whatever suits, and what ever you want
' At a guess the scaling effect may differ on different files.
'Since I don't think I can attach a suitable image in StackOverflow it really doesnt
' matter what it is, but something around 300-400 x 160 pixels would show the issue.
ImageToAdd = "C:\Temp\Excel_Logo_test.jpg"
'Create a single chart workbook
Set owB = Oxl.WorkBooks.Add(xlWBATChart)
'Get reference to the chart
Set Chrt = owB.Charts(1)
On Error GoTo Err_Handler
Chrt.Activate
'Insert a data sheet before the chart
Set DSht = owB.Sheets.Add
'Insert some dummy data
DSht.Name = "Processed Data"
DSht.Cells(1, 1) = "X"
DSht.Cells(1, 2) = "Y"
For i = 2 To 11
DSht.Cells(i, 1) = i - 1
DSht.Cells(i, 2) = (i - 1) * 2
Next i
Set Rng = DSht.Range("$A:$B")
'Various set up of chart size and orientation
Chrt.PageSetup.PaperSize = xlPaperA4
Chrt.PageSetup.Orientation = xlLandscape
Chrt.SizeWithWindow = False
Chrt.ChartType = xlXYScatterLinesNoMarkers
Chrt.Activate
'Now add the data on to the chart
Chrt.SeriesCollection.Add Source:=Rng, Rowcol:=xlColumns, SeriesLabels:=True
'Set up for some general titles etc
Set Ax = Chrt.Axes(xlValue, xlPrimary)
Ax.HasTitle = True
Ax.AxisTitle.Caption = "Y-Axis"
Chrt.HasTitle = True
Chrt.ChartTitle.Caption = "Title"
'Resize the graph area to our requirements
Chrt.PageSetup.LeftMargin = Excel.Application.CentimetersToPoints(1.9)
Chrt.PageSetup.RightMargin = Excel.Application.CentimetersToPoints(1.9)
Chrt.PageSetup.TopMargin = Excel.Application.CentimetersToPoints(1.1)
Chrt.PageSetup.BottomMargin = Excel.Application.CentimetersToPoints(1.6)
Chrt.PageSetup.HeaderMargin = Excel.Application.CentimetersToPoints(0.8)
Chrt.PageSetup.FooterMargin = Excel.Application.CentimetersToPoints(0.9)
Chrt.PlotArea.Left = 35
Chrt.PlotArea.Top = 32
Chrt.PlotArea.Height = Chrt.ChartArea.Height - 64
Chrt.PlotArea.Width = Chrt.ChartArea.Width - 70
'Place image (#1) top left corner. At this point Excel is still invisible
Chrt.Shapes.AddPicture ImageToAdd, msoFalse, msoTrue, 0#, 0#, -1, -1
'Place image (#2) more to the right. At this point Excel is still invisible
Set Pic = Chrt.Shapes.AddPicture(ImageToAdd, msoFalse, msoTrue, 300#, 0#, -1, -1)
'Now try and force the scaling.... wont work!
Pic.ScaleHeight 1#, msoTrue, msoScaleFromTopLeft
Pic.ScaleWidth 1#, msoTrue, msoScaleFromTopLeft
Oxl.Visible = True
'Place the same image (#3) lower down. Excel is now visible
Chrt.Shapes.AddPicture ImageToAdd, msoFalse, msoTrue, 0#, 150#, -1, -1
'Place the same image (#4) lower down and right. Excel still visible
Set Pic = Chrt.Shapes.AddPicture(ImageToAdd, msoFalse, msoTrue, 300#, 150#, -1, -1)
'Now try and force the scaling.... will work when visible!
Pic.ScaleHeight 1.2, msoTrue, msoScaleFromTopLeft
Pic.ScaleWidth 1.2, msoTrue, msoScaleFromTopLeft
MsgBox "First check point"
'At this point we are going to pause with Excel visible to see the difference in the 4 images
'On my system (Office 2010)....
'The first: placed when Excel was not visible has some form of image scaling applied.
' Height_Scaling = 107%,
' Width Scaling = 99%.
'The second: Like the first, but we are going to try and force the scaling. Will not work!!
' Height_Scaling = 107%,
' Width Scaling = 99%.
'The 3rd: placed when Excel was visible has NO image scaling applied.
' Height_Scaling = 100%,
' Width Scaling = 100%.
'The 4th: Like the 3rd, but forcing scaling to 120% horz and vert. Will work because visible
' Height_Scaling = 120%,
' Width Scaling = 120%.
'Now try and force the scaling (image #2).... will work when visible!
Set Pic = Chrt.Shapes(2)
Pic.ScaleHeight 1#, msoTrue, msoScaleFromTopLeft
Pic.ScaleWidth 1#, msoTrue, msoScaleFromTopLeft
MsgBox "Do what you like now. When you have finished checking in Excel, click this box and the Excel instance will close"
'Suppress save message...
Oxl.DisplayAlerts = False
'Close the Excel instance so it is not left dangling in memory...
Oxl.Quit
Exit Sub
Err_Handler:
'An ERROR. Lets clear up...
MsgBox "Error"
'Suppress save message...
Oxl.DisplayAlerts = False
'Close the Excel instance so it is not left dangling in memory...
Oxl.Quit
End Sub
Delphi XE7 (but should run on anything from Delphi 7 onwards) test app (Single form one button)
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
Vcl.OleAuto,
ExcelXP, OfficeXP;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
const
ExcelAppID = 'Excel.Application';
//File name of an image on disk we are going to place on the graph. we don't want
// to link to it, as the Excel file will be sent to someone else.
//For the purposes of the test this file can be whatever suits, and what ever you want
// At a guess the scaling effect may differ on different files.
//Since I don't think I can attach a suitable image in StackOverflow it really doesnt
// matter what it is, but something around 300-400 x 160 pixels would show the issue.
ImageToAdd = 'C:\Temp\Excel_Logo_test.jpg';
var
Oxl: Variant;
owB: Variant;
Chrt: Variant;
DSht: Variant;
i: Integer;
Rng: Variant;
Ax: Variant;
Pic: Variant;
begin
try
OxL:= CreateOleObject(ExcelAppID);
OxL.Visible:= false;
try
try
//Create a single chart workbook
owB:= Oxl.WorkBooks.Add(Integer(xlWBATChart));
//Get reference to the chart
Chrt:= owB.Charts[1];
Chrt.Activate;
//Insert a data sheet before the chart
DSht:= owB.Sheets.Add;
//Insert some dummy data
DSht.Name:= 'Processed Data';
DSht.Cells[1, 1]:= 'X';
DSht.Cells[1, 2]:= 'Y';
For i:= 2 To 11 do
begin
DSht.Cells(i, 1):= i - 1;
DSht.Cells(i, 2):= (i - 1) * 2;
end;
Rng:= DSht.Range['$A:$B'];
//Various set up of chart size and orientation
Chrt.PageSetup.PaperSize:= xlPaperA4;
Chrt.PageSetup.Orientation:= xlLandscape;
Chrt.SizeWithWindow:= False;
Chrt.ChartType:= xlXYScatterLinesNoMarkers;
Chrt.Activate;
//Now add the data on to the chart
Chrt.SeriesCollection.Add(Source:=Rng, Rowcol:=xlColumns, SeriesLabels:=True);
//Set up for some general titles etc
Ax:= Chrt.Axes(xlValue, xlPrimary);
Ax.HasTitle:= True;
Ax.AxisTitle.Caption:= 'Y-Axis';
Chrt.HasTitle:= True;
Chrt.ChartTitle.Caption:= 'Title';
//Resize the graph area to our requirements
Chrt.PageSetup.LeftMargin:= OxL.CentimetersToPoints(1.9);
Chrt.PageSetup.RightMargin:= OxL.CentimetersToPoints(1.9);
Chrt.PageSetup.TopMargin:= OxL.CentimetersToPoints(1.1);
Chrt.PageSetup.BottomMargin:= OxL.CentimetersToPoints(1.6);
Chrt.PageSetup.HeaderMargin:= OxL.CentimetersToPoints(0.8);
Chrt.PageSetup.FooterMargin:= OxL.CentimetersToPoints(0.9);
Chrt.PlotArea.Left:= 35;
Chrt.PlotArea.Top:= 32;
Chrt.PlotArea.Height:= Chrt.ChartArea.Height - 64;
Chrt.PlotArea.Width:= Chrt.ChartArea.Width - 70;
//Place image top left corner. At this point Excel is still invisible
Pic:= Chrt.Shapes.AddPicture(ImageToAdd, msoFalse, msoTrue, 0, 0, -1, -1);
//Pic:= Chrt.Shapes(1);
//Place image more to the right. At this point Excel is still invisible
Pic:= Chrt.Shapes.AddPicture(ImageToAdd, msoFalse, msoTrue, 300, 0, -1, -1);
//Pic:= Chrt.Shapes(2);
//Now try and force the scaling.... wont work!
Pic.ScaleHeight(1, msoTrue, msoScaleFromTopLeft);
Pic.ScaleWidth(1, msoTrue, msoScaleFromTopLeft);
Oxl.Visible:= True;
//Place the same image lower down. Excel is now visible
Pic:= Chrt.Shapes.AddPicture(ImageToAdd, msoFalse, msoTrue, 0, 150, -1, -1);
//Pic:= Chrt.Shapes(3);
//Place the same image lower down and right. Excel still visible
Pic:= Chrt.Shapes.AddPicture(ImageToAdd, msoFalse, msoTrue, 300, 150, -1, -1);
//Pic:= Chrt.Shapes(4);
//Now try and force the scaling.... will work when visible!
Pic.ScaleHeight(1.2, msoTrue, msoScaleFromTopLeft);
Pic.ScaleWidth(1.2, msoTrue, msoScaleFromTopLeft);
ShowMessage('First check point');
//At this point we are going to pause with Excel visible to see the difference in the 4 images
//On my system (Office 2010)....
//The first: placed when Excel was not visible has some form of image scaling applied.
// Height_Scaling = 107%,
// Width Scaling = 99%.
//The second: Like the first, but we are going to try and force the scaling. Will not work!!
// Height_Scaling = 107%,
// Width Scaling = 99%.
//The 3rd: placed when Excel was visible has NO image scaling applied.
// Height_Scaling = 100%,
// Width Scaling = 100%.
//The 4th: Like the 3rd, but forcing scaling to 120% horz and vert. Will work because visible
// Height_Scaling = 120%,
// Width Scaling = 120%.
//Now try and force the scaling.... will work when visible!
Pic:= Chrt.Shapes[2];
Pic.ScaleHeight(1, msoTrue, msoScaleFromTopLeft);
Pic.ScaleWidth(1, msoTrue, msoScaleFromTopLeft);
ShowMessage('Do what you like now. When you have finished checking in Excel, click this box and the Excel instance will close');
//Suppress save message...
Oxl.DisplayAlerts:= False;
//Close the Excel instance so it is not left dangling in memory...
Oxl.Quit;
except
//An ERROR. Lets clear up...
ShowMessage('Error');
end;
finally
//Suppress save message...
Oxl.DisplayAlerts:= False;
//Close the Excel instance so it is not left dangling in memory...
Oxl.Quit;
end;
except
raise exception.create('Excel could not be started.');
end;
end;
end.
I have tried all sorts of things, like explicitly trying to set the HeightScaling and WidthScaling properties of the image, but when Excel is not visible these do not work.
As far as I can see this is a bug in Excel, but if anybody has another idea I would love to hear it, and particularly if you have a workaround that does not involve Excel being visible. (I have tried making it visible just for the adding of the picture, and this works fine, but again a quick flash of Excel will look really unprofessional in our application, perhaps even more unprofessional).
The test code was written as a macro in Word 2010. [You have to make sure that you add Excel in the Project/References section]. [As mentioned in the code you will need to provide an image of some sort, since I don't think I can attach files in StackOverflow...]. It creates a spread sheet with a chart, adds a small amount of data, and charts it. Then 4 copies of the image are added
1. Simple Add (Excel hidden)
2. Simple add (Excel hidden), and then try and force the scaling
Show Excel
3. Simple Add
4. Simple Add, and then try and force the scaling (120%/120%)
A message box is then shown to halt the macro to allow inspection of the image properties on the chart area.
Images 1 and 2 are both show with scaling 107%/99%
Images 3 and 4 show as (100%/100%) and (120%/120%) so both 3 and 4 are correct.
When the message box is cleared (and with Excel now visible), the scaling on image 2 is adjusted to 100%/100%, and this now works correctly.
Another message box to allow checking this and finally Excel is closed.
I don't think the InsertPicture method is an option, as this links to the image file rather than embedding it. The final files must work properly as stand-alone entities so file links can not be used.
I would also prefer not to try workarounds like using the clipboard and the paste method. Nuking the clipboard can seriously upset users doing other things at the same time as this process is running.
Thanks in anticipation.
I am generating an excel Pie Chart from Delphi and all goes well except when I want the labels (with the values) to be sent to the exterior of the Pie
So my generated pie looks like this:
And I want to obtain this:
So my code looks like this:
if not CreateExcel then exit;
VisibleExcel(false);
if AddWorkBook then
begin
SelectSheet(1);
end;
AddChart(ChartName,xl3DPieExploded);//xl3DColumn);
SetSourceData(1{ChartName},2,'A1:B5',xlColumns);
randomize;
VisibleExcel(false);
E.ActiveWorkbook.Sheets.Item[2].Cells[1,1] :='Some text';
E.ActiveWorkbook.Sheets.Item[2].Cells[1,2] :='Chart title';
E.ActiveWorkbook.Sheets.Item[2].Cells[2,1] :='Mijloace de productie';
E.ActiveWorkbook.Sheets.Item[2].Cells[3,1] :='Mediul de munca';
E.ActiveWorkbook.Sheets.Item[2].Cells[4,1] :='Sarcina de munca';
E.ActiveWorkbook.Sheets.Item[2].Cells[5,1] :='Executant';
E.ActiveWorkbook.Sheets.Item[2].Cells[2,2] := FloatToStr(proc1)+'%';
E.ActiveWorkbook.Sheets.Item[2].Cells[3,2] := FloatToStr(proc2)+'%';
E.ActiveWorkbook.Sheets.Item[2].Cells[4,2] := FloatToStr(proc3)+'%';
E.ActiveWorkbook.Sheets.Item[2].Cells[5,2] := FloatToStr(proc4)+'%';
// remove the legend
E.ActiveWorkbook.Charts.Item['Chart1'].Select;
E.ActiveChart.Legend.Select;
E.Selection.Delete;
// apply labels
E.ActiveWorkbook.Charts.Item['Chart1'].Select;
E.ActiveChart.SeriesCollection(1).ApplyDataLabels;
// formatting chart labels.
E.ActiveChart.ApplyDataLabels(xlDataLabelsShowLabelAndPercent, false,true,
true{HasLeaderLines: OleVariant; }, false {ShowSeriesName: OleVariant;},
true {ShowCategoryName: OleVariant; }, true {ShowValue: OleVariant;},
false {ShowPercentage: OleVariant; }, false {ShowBubbleSize: OleVariant;},
'; '{Separator: OleVariant; } );
So it has LeaderLines, this far it works, but now I want to see the labels outside of the pie, distanced.
How can I do this. I recorded a Macro in Excel but I cannot seem to be able to apply it to Delphi. The macro code that I want to use in Delphi is:
ActiveSheet.ChartObjects("Chart 1").Activate
Selection.Position = xlLabelPositionOutsideEnd
The Series object has the DataLabels method which if you omit the optional index parameter can return a DataLabels object which has the Position property, that you might see in your recorded macro. So I believe you can add the following line to the end of your posted code:
E.ActiveChart.SeriesCollection(1).DataLabels.Position := xlLabelPositionOutsideEnd;