C# How to update Powerpoint chart without opening Excel [duplicate] - c#

Slide.Shapes.AddChart() automatically opens Excel. Even if I quickly do Chart.ChartData.Workbook.Application.Visible = false, it still shows a little while. This makes automating chart creation error-prone as the user has to try not to touch the Excel applications that keeps popping up.
Opening a presentation with WithWindow = false will still open Excel when creating new charts.

This behavior is "by design" and Microsoft is not interested in changing. This is the way the UI functions.
What you could do would be to create the chart in Excel (using either the interop or OpenXML), then import (insert) that file into PowerPoint.
Check this link from MSDN

Here's a possible work around.
Sub ChartExample()
Dim s As Shape
Set s = Application.Presentations(1).Slides(1).Shapes.AddOLEObject(ClassName:="Excel.Chart")
End Sub
You would then manipulate the chart you added via the s.OLEFormat.Object. I only experimented slightly, but it does not open an external Excel application and I did not see any extreme flickering unless I activated the object. A trade off is that at least in Powerpoint 2010, you need to convert it to use all of the features. If this doesn't work you could always try web components.
Edit:
I don't understand why this method causes a problem, but to try to assist further here is a little more code that shows actually manipulating the object. This was written with objects instead of workbooks etc, so that no references need to be made. It only demands the user have Excel on their machine.
Option Explicit
Const xlcolumns = 2
Sub ChartExample()
Dim s As Shape
Dim wb As Object, chart As Object, data As Object
Set s = Application.Presentations(1).Slides(1).Shapes.AddOLEObject(ClassName:="Excel.Chart")
Set wb = s.OLEFormat.Object
Set chart = wb.Sheets(1)
Set data = wb.Sheets(2)
'Set the range for the chart data
chart.setsourcedata Source:=data.Range("A1:C7"), PlotBy:= _
xlcolumns
'Update data values for the chart
data.Range("B1").Value = "Column Label 1"
data.Range("C1").Value = "Column Label 2"
data.Range("A2:C7").clearcontents
data.Range("A2").Value = "Row Label"
data.Range("B2").Value = 7
data.Range("C2").Value = 11
End Sub

I would suggest another methdology to over come the same.
In the powerpoint VBA add refrences to "Microsoft Excel 12.0 Object Library"
Ensure the user that for this operation none of the excel must be open via yuser form popup before the operation.
In the VBA create an excel and set its parameters in the following code
Add the powerpoint chart, the user wouldnt be able to see the opening of the underlying excel sheet upon adding chart excet the excel tabs which can be controled via code.
Sample Code:
Option Explicit
Sub AddExcelChartSample()
Dim xlApp As Excel.Application, xlWkbk As Excel.Workbook
Dim pres As PowerPoint.Presentation, sld As PowerPoint.Slide, iCount As Integer, chtShape As PowerPoint.Shape
'Open up the excel instance and set parameters
Set xlApp = New Excel.Application
With xlApp
.WindowState = xlNormal
.Top = -1000
.Left = -1000
.Height = 0
.Width = 0
End With
Set sld = PowerPoint.ActiveWindow.View.Slide
For iCount = 1 To 10
Set chtShape = sld.Shapes.AddChart(xlLine)
Set xlWkbk = chtShape.Chart.ChartData.Workbook
With xlWkbk
.Sheets(1).Range("A2").Value = "Test 1"
.Sheets(1).Range("A3").Value = "Test 2"
.Sheets(1).Range("A4").Value = "Test 3"
End With
chtShape.Chart.Refresh
xlWkbk.Close False
Next iCount
xlApp.Quit
End Sub

Related

OpenXML refresh connectors on a PowerPoint Slide automatically

I have a simple scenario where I programmatically create a presentation with a slide using OpenXML SDK 2.5 and c#. The slide has 2 shapes on it as well as a connector that connects these 2 shapes.
When I open the presentation in PowerPoint both shapes and the connector shown, but the connector is not positioned properly between the shapes. When I drag one of the shapes on the slide, PowerPoint immediately refreshes the connector and puts it into the correct position.
My question: is it possible to create an openxml PowerPoint slide that automatically refreshes the connector positions when the file is opened?
Thank you
The solution I came up with for this problem may seem rather hack-ish, but as far as I can tell there isn't a better way. The problem is that PowerPoint controls connector placement internally and doesn't expose any methods to refresh them. In testing, I was amazed to discover that PowerPoint will dynamically change the connector type during the refresh if necessary.
In order to get the refresh to happen, I had to write a VBA macro in a .pptm file and call it from my C# code. I added a module and put the function there so it wouldn't be associated with a particular slide.
This code moves each shape on a slide in order to cause the connector refresh to fire. It looks for shapes inside of groups too. It is filtering on triangle and diamond shapes.
I avoided using ActivePresentation in the code because I want to hide PowerPoint while the macro runs.
Public Sub FixConnectors()
Dim mySlide As Slide
Dim shps As Shapes
Dim shp As Shape
Dim subshp As Shape
Set mySlide = Application.Presentations(1).Slides(1)
Set shps = mySlide.Shapes
For Each shp In shps
If shp.AutoShapeType = msoShapeIsoscelesTriangle Or shp.AutoshapeType = msoShapeDiamond Then
shp.Left = shp.Left + 0.01 - 0.01
End If
If shp.Type = mso.Group Then
For Each subshp In shp.GroupItems
If subshp.AutoShapeType = msoShapeIsoscelesTriangle Or subshp.AutoshapeType = msoShapeDiamond Then
subshp.Left = subshp.Left + 0.01 - 0.01
End If
Next subshp
End If
Next shp
Application.Presentations(1).Save
End Sub
Next comes the C# code to run the macro using PowerPoint Interop. Closing and reopening the file as a Windows process allows the garbage collector to clean up any handles that Interop has. In testing, the finalizers could take several seconds to run so the GC calls happen after reopening the file so the application doesn't appear to hang.
using System;
using System.Diagnostics;
using OC = Microsoft.Office.Core;
using PP = Microsoft.Office.Interop.PowerPoint;
string filePath = "C:/Temp/";
string fileName = "Template.pptm";
// Open PowerPoint in hidden mode, run the macro, and shut PowerPoint down
var pptApp = new PP.Application();
PP.Presentation presentation = pptApp.Presentations.Open(filePath + fileName, OC.MsoTriState.msoFalse, OC.MsoTriState.msoFalse, OC.MsoTriState.msoFalse);
pptApp.Run(filename + "!.FixConnectors");
presentation.Close();
presentation = null;
pptApp.Quit();
pptApp = null;
// Reopen the file through Windows
Process.Start(filePath + fileName);
// Clear all references to PowerPoint
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();

Duplicate an Excel chart and move it to another sheet

I am using the C# Excel interop and I want to create a copy of a chart from one sheet but I want this copy on another sheet. I have tried the following:
Excel.ChartObject chartTemplate = (Excel.ChartObject)sheetSource.ChartObjects("chart 1");
object o = chartTemplate.Duplicate();
Excel.ChartObject chart = (Excel.ChartObject)sheetSource.ChartObjects("chart 2");
chart.Name = "Skew" + expiry.ToString("MMMyy");
range = sheetDestination.Range["T" + chartRowCoutner.ToString()];
chart.Chart.Location(Excel.XlChartLocation.xlLocationAsObject, range);
But when I try this, the last line throws an error:
An unhandled exception of type 'System.Exception' occurred in projectname.exe
Additional information: Error reading Excel file C:\ ...the file path...\template.xlsx: Value does not fall within the
expected range.
I have also tried passing a sheet in instead of a range:
chart.Chart.Location(Excel.XlChartLocation.xlLocationAsObject, sheetDestination);
but this gives the same error. I can't understand the reason for the error or how to fix it / bypass it.
I am trying to avoid bringing the clipboard into this, but even if I try copying and pasting, I can still only paste it as an image, which is really not ideal:
Excel.ChartArea chartArea = chart.ChartArea;
chartArea.Copy();
range = sheetDestination.Range["T" + chartRowCoutner.ToString()]; // Note that chart is not on the sheet sheetDestination
range.PasteSpecial(Excel.XlPasteType.xlPasteAll);
The only other solution I can think of now is to do this in VBA and then execute the macro via the interop. But surely it can be done in a clean way just using the interop without the clipboard.
You've already got the solution but instead of giving you a fish for a day I'll give you a proper answer that will help you with any C# Excel coding task.
The C# Interop Model for Excel is almost identical to the VBA Excel Model.
This means it's trivial to convert VBA recorded macros to C#. Let's try this with an exercise like moving a chart to a different sheet.
In the Developer Tab in Excel click Record Macro > right click Chart > select Move Chart > choose Object in: Sheet2 > click OK > click Stop Macro Recording.
To see the recorded Macro press Alt + F11 to bring up the VB Editor:
See in the above screenshot how VBA shows you the second parameter for Location() is Name and it's actually a string argument...
Let's convert this VBA Macro to C#:
EDIT by #Ama
The advice below is outdated, there's actually no need to worry about releasing COM objects, this is done automatically at RELEASE mode (DEBUG mode does not). See Hans Passant's answer to "Clean up Excel Interop Objects with IDisposable".
The trick here is: never use 2 dots with com objects.
Notice how I could have written:
var sheetSource = workbookWrapper.ComObject.Sheets["Sheet1"];
but that has two dots, so instead I write this:
var workbookComObject = workbookWrapper.ComObject;
var sheetSource = workbookComObject.Sheets["Sheet1"];
Ref: How do I properly clean up Excel interop objects?
You will see the AutoReleaseComObject code in the above QA that projects like VSTOContrib use.
Here is the complete code:
using Microsoft.Office.Interop.Excel;
...
var missing = Type.Missing;
using (AutoReleaseComObject<Microsoft.Office.Interop.Excel.Application> excelApplicationWrapper = new AutoReleaseComObject<Microsoft.Office.Interop.Excel.Application>(new Microsoft.Office.Interop.Excel.Application()))
{
var excelApplicationWrapperComObject = excelApplicationWrapper.ComObject;
excelApplicationWrapperComObject.Visible = true;
var excelApplicationWrapperComObjectWkBooks = excelApplicationWrapperComObject.Workbooks;
try
{
using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapperComObjectWkBooks.Open(#"C:\Temp\ExcelMoveChart.xlsx", false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
{
var workbookComObject = workbookWrapper.ComObject;
Worksheet sheetSource = workbookComObject.Sheets["Sheet1"];
ChartObject chartObj = (ChartObject)sheetSource.ChartObjects("Chart 3");
Chart chart = chartObj.Chart;
chart.Location(XlChartLocation.xlLocationAsObject, "Sheet2");
ReleaseObject(chart);
ReleaseObject(chartObj);
ReleaseObject(sheetSource);
workbookComObject.Close(false);
}
}
finally
{
excelApplicationWrapperComObjectWkBooks.Close();
ReleaseObject(excelApplicationWrapperComObjectWkBooks);
excelApplicationWrapper.ComObject.Application.Quit();
excelApplicationWrapper.ComObject.Quit();
ReleaseObject(excelApplicationWrapper.ComObject.Application);
ReleaseObject(excelApplicationWrapper.ComObject);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
private static void ReleaseObject(object obj)
{
try
{
while (System.Runtime.InteropServices.Marshal.ReleaseComObject(obj) > 0);
obj = null;
}
catch (Exception ex)
{
obj = null;
Console.WriteLine("Unable to release the Object " + ex.ToString());
}
}
I know Releasing all the Objects, using GC.Collect and not using two dots when assigning seems over the top but at least when I quit the instance of Excel the process is freed, I don't have to programmatically kill the Excel process!
Ref: Microsoft KB: Office application does not quit after automation from .NET client
From the MSDN documentation here:
https://msdn.microsoft.com/en-us/library/microsoft.office.tools.excel.chart.location.aspx
it states that for the Name parameter of type object:
Name
Type: System.Object
The name of the sheet where the chart is embedded if Where is xlLocationAsObject or the name of the new sheet if Where is xlLocationAsNewSheet.
This is somewhat misleading from the example at the bottom of the same linked page. It would appear from the example given, that you should actually pass a string of the sheet name. The pertinent line from the example is copied below (the example is for copying to a new sheet):
chart1.Location(Excel.XlChartLocation.xlLocationAsNewSheet,
"Sales");
So, for moving to an existing sheet, I would do:
chart1.Location(Excel.XlChartLocation.xlLocationAsObject,
"ExistingSheetName");
Do NOT pass a range, workbook or worksheet object. Try a string of the sheet name.
Now, from the same MSDN document page linked above, if you want to reposition the chart within the page once you have moved it to another sheet, there are additional instructions, repeated here for convenience:
If you want to move a chart to another position on a sheet, use the P:Microsoft.Office.Interop.Excel.ChartArea.Top property and P:Microsoft.Office.Interop.Excel.ChartArea.Left property of the ChartArea. You can get the ChartArea object of the Chart by using the ChartArea property.
If you're moving a chart to an existing sheet, be careful not to overlap your chart over existing data. If so, you will have to code around that separately.
This isn't the answer to the question you asked, but might be fruitful
if you're making a copy and editing it for different variations THIS IS NOT A SOLUTION
if you're truly just copying a chart then I recommend using Excel's "Camera" function instead. It basically creates a window into another sheet - you can do this programmatically and it's well documented, but a little known feature of excel I thought I'd be remiss if I didn't point out.
-E
If you are looking to make edits & the question is still open let me know that in a comment - I've done this before I just need to look back in my workbook and see exactly how I did it.
'Camera option is nice because it doesn't 'recalculate' the data - so I imagine it operates faster; a concern in large workbooks.

How to load vba macros to the excel file from a text file by a C# code?

My C# code has to create an Excel file with two Worksheets and output some data over there. Besides data columns, the Sheet 1 has to be enabled with a VBA macros which would allow a user to perform some mathematical calculations with provided data upon clicking on a particular cell. This VBA macros are stored in a text file, like C:\VBA_MACROS\VBA1.txt.
Right now I can do it manually, i.e.
C# code creates an Excel file and populates it with data.
I do a right click on Sheet1 and select an option "View Code".
I click on the button "Insert" from the Microsoft Visual Basic for Applications Menu and load the file C:\VBA_MACROS\VBA1.txt.
I close the VBA code window.
Question: can steps 2 - 4 be performed automatically by a C# code as well as the step 1? In this case a user would not have to perform them manually which would be a way more comfortable for her.
To be exact, this is how the application is created:
Excel.Application application = new Excel.Application();
Excel.Workbook workbook = application.Workbooks.Add();
Excel.Worksheet worksheet = workbook.Sheets[1];
Excel.Worksheet worksheet2 = workbook.Sheets[2];
// output data to the worksheets
DataTable2Worksheet(tableMain, worksheet, verSize);
DataTable2Worksheet(tableExtra, worksheet2, 0);
// output workbook to the file
string fileDir = #"D:\MyTests\ExcelTests\";
Output2File(fileDir, workbook);
DataTable2Worksheet and Output2File functions are quite trivial, but how to attach the content of the text file to worksheet = workbook.Sheets[1] by using AddFromFile method?
You'll need a reference to Microsoft.Vbe.Interop;.
Then you need to get a handle on the module you want to insert into.
Then you can just use the CodeModule.AddFromFile method to insert the code in your text file into the module.
VBE.VBProjects("NameOfProject").VBComponents.Item("NameOfWorksheet").CodeModule.AddFromFile("C:\Path\to\file.txt");
The default name for a newly created project is "VBAProject" and you the name of the component for a sheet is the name of the sheet.
So, for your particular case, you could add this line of code at the end of your snippet to insert the VBA into Sheet1.
application.VBE.VBProjects("VBAProject").VBComponents.Item("Sheet1").CodeModule.AddFromFile("C:\VBA_MACROS\VBA1.txt");
I just learned that another option is to use the VBProject property of the Workbook, which makes the call a little cleaner.
workbook.VBProject.VBComponents.Item("Sheet1").CodeModule.AddFromFile("C:\VBA_MACROS\VBA1.txt");

Extracting Excel workbook name before it actually opens

I am trying to get the name of the workbook before it actually opens up.
((Excel.AppEvents_Event)this.Application).WorkbookOpen += new Excel.AppEvents_WorkbookOpenEventHandler(App_WorkBookOpen);
private void App_WorkBookOpen(Excel.Workbook Wb)
{
System.Windows.Forms.MessageBox.Show("Shakti " + " " + Wb.Name);
}
With the handler as shown above, Excel application shows the workbook name when it is opened completely.My intention is to do some formal check before it is actually opened up and data is shown to the user.
Is there any way or mechanism to extract the file name before the contents are loaded on to Excel and shown to the user? Any sort of help is highly appreciated.Thanks.
AFAIK you can't do that. But like I mentioned in my comment you could hide the workbook the moment it is visible. So the user will see the workbook open for a split second and then go invisible. In that split second you can read the name of the workbook and then hide the workbook.
Based on your calculations/conclusion you can then close/unhide the workbook as required.
You can hide the workbook using
Wb.Windows[1].Visible = false;
No you can't.
You anyway could create a Macro on a WorkBook Module with Open class tag as here:
Private Sub Workbook_Open()
Dim ws As Workbooks
For Each ws In ActiveWorkbook.Worksheets
MsgBox ws.Name
Next
ActiveWorkbook.Worksheets.Close
End Sub
Then call this sub via c# on opening the file, this sub runs before loading the workbook then it closes it. It has not that sense, because you'll never access the wb again...
Maybe with some tweaking here and there you could accomplish your task, but it depends to you.
Hope it helps...
Isn't Wb.name the same as the filename? In which case, since you must know the filename/location in order to open it, you can check it beforehand?

How can I read an Excel 2010 file in my C# code using a DLL?

UPDATE1:
I am using Excel 2010 and I've searched the web and found thousands upon thousands of ways to do this via win form, console, etc. But I can't find a way to do this via DLL. and none of the sample on-line is complete all in bit and pieces.
UPDATE END
I have looked and goggled but did not get the specific what i am looking for, as show below the excel sample sheet.
i'm looking a way to read and store the each cell data in a variable
i have started something like this:
Workbook workbook = open(#"C:\tmp\MyWorkbook.xls");
IWorksheet worksheet = workbook.Worksheets[0];
IRange a1 = worksheet.Cells["A1"];
object rawValue = a1.Value;
string formattedText = a1.Text;
Console.WriteLine("rawValue={0} formattedText={1}", rawValue, formattedText);
Your code can work with a couple changes.
One thing to remember is that Excel worksheets are 1-based, not 0-based (and use Worksheet instead of IWorksheet):
Worksheet worksheet = workbook.Worksheets[1];
And to get a range, it is easiest to call get_Range() on the worksheet object (and use Range instead of IRange):
Range a1 = worksheet.get_Range("A1");
With those two lines of code changed, your example will work fine.
UPDATE
Here is a "complete" example:
Right-click your project in the solution explorer and click "Add
Reference".
Click on the COM tab and sort the list by Component Name. Find "Microsoft Excel 14.0 Object Library" in the list and select it. Click OK.
In the code file where you want this to run, add a using Microsoft.Office.Interop.Excel;
Use this code, which I've modified as little as possible from your example:
var excel = new Microsoft.Office.Interop.Excel.Application();
Workbook workbook = excel.Workbooks.Open(#"C:\tmp\MyWorkbook.xls");
Worksheet worksheet = workbook.Worksheets[1];
Range a1 = worksheet.get_Range("A1");
object rawValue = a1.Value;
string formattedText = a1.Text;
Console.WriteLine("rawValue={0} formattedText={1}", rawValue, formattedText);
Excel.Sheets sheets = workbook.Worksheets;
Excel.Worksheet worksheet = (Excel.Worksheet)sheets.get_Item(1);
System.Array myvalues;
Excel.Range range = worksheet.get_Range("A1", "E1".ToString());
myvalues = (System.Array)range.Cells.Value;
If you don't want to be in a war with com components and registering dlls,
the best way to read excel is Excel Reader for .NET
I have been using it for so long time , and I can say it just works.
and excelReader.IsFirstRowAsColumnNames property makes everything easy.
You can play your data within a dataset.

Categories