Exporting Excel Range as image (VB.NET) - vb.net

I have a working excel vba macro that does what I want from here and I am trying to convert it to VB.NET.
The code from VBA:
Sub bah()
''' Set Range you want to export to file
Dim rgExp As Range: Set rgExp = Range("B2:C6")
''' Copy range as picture onto Clipboard
rgExp.CopyPicture Appearance:=xlScreen, format:=xlBitmap
''' Create an empty chart with exact size of range copied
With ActiveSheet.ChartObjects.Add(Left:=rgExp.Left, Top:=rgExp.Top, _
Width:=rgExp.Width, Height:=rgExp.Height)
.Name = "ChartVolumeMetricsDevEXPORT"
.Activate
End With
''' Paste into chart area, export to file, delete chart.
ActiveChart.Paste
ActiveSheet.ChartObjects("ChartVolumeMetricsDevEXPORT").Chart.Export "C:\Users\ajohnson\Desktop\workdamnit.jpg"
ActiveSheet.ChartObjects("ChartVolumeMetricsDevEXPORT").Delete
End Sub
What this does is take an excel range and then put it into a chart that is a copy of the range and save it as a JPG.
Here is my most recent attempt at making it VB.NET:
Dim xlApp As New Excel.Application
Dim xlWorkBook As Excel.Workbook
Dim xlWorkSheet As Excel.Worksheet
Dim xlRange As Excel.Range
xlWorkBook = xlApp.Workbooks.Open("C:\test.xlsx")
xlWorkSheet = xlWorkBook.Sheets("Sheet1")
xlRange = xlWorkSheet.Range("B2:C6")
With xlWorkSheet.ChartObjects.add(xlRange.Left, xlRange.Top, xlRange.Width, xlRange.Height)
.name = "Chart1"
.activate()
End With
xlWorkSheet.ChartObjects("Chart1").Paste()
xlWorkSheet.ChartObjects("Chart1").chart.export(Filename:="C:\Users\ajohnson\Desktop\saveit.jpg")
xlWorkSheet.ChartObjects("Chart1").delete()
I am running into trouble converting the ActiveChart.Paste method. I can't get it to work in VB.NET. It either throws an error or It just leaves an empty box when I do it in VB.NET (if I add .chart before the paste it runs, but doesn't paste any values), but in VBA it fills in the values of interest. I have tried creating a chart object, but that did not seem to work either.
I feel like I am close to having it sorted out, but I can't quite get it. I suppose I could leave it as a VBA macro and call it from VB.NET, but that seems absurd on some level. Any help would be greatly appreciated. I am also open to different approaches, its just this is the thing I came across that worked well in VBA, so I figured it was a good starting point.
Thanks as always!

I just had to hit the MSDN up a little harder to get there. Turns out you have to put the chartobject inside a chart, the code I got working looks like:
xlRange = xlWorkSheet.Range("B2:C6")
xlRange.CopyPicture(Excel.XlPictureAppearance.xlScreen, Excel.XlCopyPictureFormat.xlPicture)
Dim oChtobj As Excel.ChartObject = xlWorkSheet.ChartObjects.add(xlRange.Left, xlRange.Top, xlRange.Width, xlRange.Height)
Dim oCht As Excel.Chart
oCht = oChtobj.Chart
oCht.Paste()
oCht.Export(Filename:="C:\saveit.jpg")
oChtobj.Delete()
I was going to delete the question, since it got solved by me so quickly (this ignores the decent bit of time I spent before I posted it here), but when I search for a problem like mine it comes to this page, so maybe this will help someone in the future. If you are looking to copy a range from excel to a jpg for some reason (perhaps attaching it to the body of an outlook email, because that is what I am doing), this should work for you.

And the C# equivalent requires the call to Activate() or COMException will be thrown
Excel.Range xlRange = xlWorkSheet.Range("B2:C6");
range.CopyPicture(Excel.XlPictureAppearance.xlScreen, Excel.XlCopyPictureFormat.xlPicture);
Excel.ChartObject chartObj = myWorksheet.ChartObjects().Add(range.Left, range.Top, range.Width, range.Height);
chartObj.Activate(); // Don't Forget!
Excel.Chart chart = chartObj.Chart;
chart.Paste();
chart.Export(#"C:\image.png");
chartObj.Delete();

Related

VBA Word: Change Data of charts

I want to change the data of a chart in a Word Document, but I can't find the right way to address my charts. I tried several techniques, but nothing worked. (I´d love to open a ExcelSheet in which I can just change the Data)
So to put it all together: I want to change the data (not the source), of a MS Word chart, which looks like that:
Edit(13.8.):
After request, I try to give you some "reference Code" to work with.
Sub ChangeChart()
Dim aktDocument As Document
Dim chrt As Chart
Dim SourceSheet As Excel.Worksheet
Set aktDocument = ActiveDocument
Set SourceSheet = aktDocument.Shapes(1).Chart.OpenSourceData 'I know it´s not that easy
SourceSheet.Range("B5") = newStuff
aktDocument.Shapes(1).Chart.SetSourceData = SourceSheet
End Sub
I know this may sounds utopic and ridiculous, but I just don´t know, how to address the chart in the right way, or to even work with it properly.
Edit(15.08):
Even after recreating the old charts, the following code is not able to find a shape which has a chart. And therefore it stops when the index is out of range.
Sub Test()
i = 0
Do While i < 100
i = i + 1
If ActiveDocument.Shapes(i).HasChart Then
MsgBox "found one!"
End If
Loop
End Sub
Solution(30.08.):
The answer from #Cindy Meister was the solution to my problem. After further working with it, I came to the problem, that the ChartData always opens on the screen, while running the code.
Just for reference this question was my workaround.
All Office applications use the Excel engine to create and manage charts. In Word, charts can be formatted in-line with the text or with text wrap formatting. In the former case, a chart object needs to be addressed via the InlineShapes collection, in the latter via the Shapes collection.
Since your sample code uses Shapes(1) I've used that in the code snippet below. If it's not certain that the first Shape in the document is the chart, but you've assigned the Shape a name, you can use that as the index value (for example Shapes("MyChart"). Or you can loop the Shapes collection and check HasChart.
HasChart returns True if the Shape (or InlineShape) is a Chart. It's then possible to set Shape.Chart to an object variable. The chart's data can be accessed using Chart.ChartData.Activate - if you don't use Activate it's not possible to access the data when the chart's worksheet is stored in the Word document. Only then can Chart.ChartData.Workbook return a workbook object, and through that the worksheet can be accessed using ActiveSheet. From that point on, it's like working with the Excel object model.
Sub ChangeChart()
Dim aktDocument As Document
Dim shp As Word.Shape
Dim chrt As Word.Chart
Dim wb As Excel.Workbook, SourceSheet As Excel.Worksheet
Set aktDocument = ActiveDocument
Set shp = aktDocument.Shapes(1)
If shp.HasChart Then
Set chrt = shp.Chart
chrt.ChartData.Activate
Set wb = chrt.ChartData.Workbook
Set SourceSheet = wb.ActiveSheet
SourceSheet.Range("B5").Value2 = newData
End If
End Sub

VB.NET - Programatically close the chart data object

My program is automating PowerPoint to loop through a series of chart parameters and create a new chart per parameter set. So far it works well for the first chart - however, it throws an error when attempting to create a second chart because the chart data grid is already open, and I can't find a method to properly close or dispose of the data grid after generating the graph.
Abridged code:
Imports Powerpoint = Microsoft.Office.Interop.PowerPoint
Imports Excel = Microsoft.Office.Interop.Excel
Private Sub generatePowerPoint(Qnum As String)
Try
'Create PowerPoint object and assign a presentation / slide to it
Dim oApp As Powerpoint.Application
Dim oPres As Powerpoint.Presentation
Dim oSlide As Powerpoint.Slide
oApp = New Powerpoint.Application()
oApp.Visible = True
oApp.WindowState = Powerpoint.PpWindowState.ppWindowMinimized
oPres = oApp.Presentations.Add
'Prepare to generate charts based on parameters in a listbox
Dim slideCount = lbQuestions.Items.Count
For slideN = 1 To slideCount
'Add a blank slide per graph request
oSlide = oPres.Slides.Add(slideN, Powerpoint.PpSlideLayout.ppLayoutBlank)
'Create a new shape object for each slide
Dim chartShape(slideCount) As Powerpoint.Shape
' What's causing the error: assign a chart object to the next shape object.
' This works for the first slide, but then throws an error that the PowerPoint
'Chart Data Grid is still open, preventing it from creating a new chart.
chartShape(slideN - 1) = oSlide.Shapes.AddChart2(-1, ChartFind(chartType), 50, 50, 775, 410)
Dim cData = chartShape(slideN - 1).Chart.ChartData 'Activate to refresh
Dim workbook = cData.Workbook
workbook.Application.Visible = False
Dim datasheet = workbook.Worksheets(1)
Dim colNumber As Integer = 2
Dim firstRowNumber As Integer = 2
datasheet.rows.clear()
datasheet.columns.clear()
For r = categoryNames.Count - 1 To 0 Step -1
datasheet.Cells(r + firstRowNumber, 1) = categoryNames(r)
Next
... Code to assign data and format the chart object ...
'Refresh the range accepted by the chart object
chartShape(slideN-1).Chart.Refresh
'Loop again
Next
I've spent some time going through the PowerPoint Interop docs and the PowerPoint Chart Object Model docs on msdn (e.g. https://learn.microsoft.com/en-us/previous-versions/office/developer/office-2010/ff760412(v=office.14), https://msdn.microsoft.com/en-us/vba/powerpoint-vba/articles/chartdata-object-powerpoint), and it seems that there's while there's a method to call the Chart Data Grid (chartdata.activate()) , there isn't a method to close the Chart Data Grid.
The exact error message thrown is "System.Runtime.InteropServices.COMException (0xBFFF64AA): The chart data grid is already open in 'Presentation 1 - PowerPoint'. To edit the data for this chart, you need to close it first. at Microsoft.Office.Interop.PowerPoint.Shapes.AddChart2( ..."
Does anyone have a suggestion?
Solved, mostly. For those who may have the same issue:
chartShape.Chart.ChartData.Workbook.close()
This is an undocumented method / IntelliSense will not provide it (hence the capitalization on Close), but after opening a chart object and editing the data, make sure you finish the code block with this before attempting to create a new chart object.
Now, this doesn't work if the open workbook isn't the one you opened (so for example, I can't test if there is a workbook opened by the user, and if so, close it). I'm resolving this issue by encapsulating the AddChart2 method in a Try Catch method, and if an error is thrown I let the user know to close the window and exit the subroutine so the program doesn't crash.

PowerPoint crashes while running excel vba

I have currently written an Excel VBA code which creates 40+ PowerPoint Slides.
However, while running this code PowerPoint crashes after creating 22 slides.
This issue only occurs when the VBA code is run at one go. Because, when I try to run the same code line by line it runs successfully till end.
For me this is something weird. Do we have any suggestion for this problem ?
=> My code till creation of slide 2 is listed below (thereafter it create the other slides one after another till 43rd Slide)
Regards,
Alok
Sub test25()
Dim pApp As PowerPoint.Application
Dim pPres As PowerPoint.Presentation
Dim pSlid As PowerPoint.Slide
Set pApp = New PowerPoint.Application
pApp.Visible = True
pApp.Activate
Set pPres = pApp.Presentations.Open("C:\.....\Template.pptx")
pPres.Slides(1).Select
Sheets("S01").Select
ActiveSheet.Range("A1:P27").Select
ActiveSheet.Shapes.SelectAll
Selection.copy
pPres.Slides(1).Shapes.PasteSpecial DataType:=wdPasteText
pPres.Slides(2).Duplicate
pPres.Slides(2).Select
Sheets("S02").Select
ActiveSheet.Range("A1:P27").Select
ActiveSheet.Shapes.SelectAll
Selection.copy
pPres.Slides(2).Shapes.PasteSpecial DataType:=wdPasteText
End Sub
I see multiple potential issues, some of which are just code improvements.
On the line:
pPres.Slides(2).Duplicate
You are referring to slide 2, but you have not yet created slide 2 (as this is the line that creates it). Change this to:
pPres.Slides(1).Duplicate
I don't see how your code is running, even line by line, without that...unless 'template.pptx' already has all of the slides (in which case, why are you duplicating? I assumed 'template.pptx only contained the first slide)
Initially I suspected a race condition, but typically VBA handles these well. To be sure, and just general good practice, you may want to use a variable to refer to the current slide, instead of just a number (you already have the variable declared)
Set pSlid = pPres.Slides(1)
Sheets("S01").Select
ActiveSheet.Range("A1:P27").Select
ActiveSheet.Shapes.SelectAll
Selection.copy
pSlid.Shapes.PasteSpecial DataType:=wdPasteText
Set pSlid = pSlid.Duplicate
...
Also for your own convenience, you way want to look into using a loop, like:
Set pSlid = pPres.Slides(1)
For i = 1 to 43
Sheets("S" & Format(CStr(i), "00")).Select
ActiveSheet.Range("A1:P27").Select
ActiveSheet.Shapes.SelectAll
Selection.copy
pSlid.Shapes.PasteSpecial DataType:=wdPasteText
Set pSlid = pSlid.Duplicate
Next

Trying to convert a chart in Excel into a picture to export that sheet away from its source data

The saga of the steel plate calculator continues. All the calculations work really well, to no small extent thanks to you lot here on SO, but in the final export stage I find that the graph showing utilisation optimisation loses its data if the source file is no longer open.
I'm looking, then, for a way to keep the graph static after export, ideally without having to copy the data fields across. The ideal would be to convert it into a picture, maintaining its location and size.
I found this here on SO, but it creates a new graph shape, apparently formatted as a pie chart:
Sub PasteGraph2()
Dim ws As Worksheet
Dim cht As Chart
Set ws = ActiveSheet
Set cht = ws.Shapes.AddChart.Chart
With cht
.SetSourceData ws.Range("$B$21:$C$22")
.ChartType = xl3DPie
.ChartArea.Copy
End With
ws.Range("A2").PasteSpecial xlPasteValues
cht.Parent.Delete
End Sub
I also tried this, found on a site of Powerpoint macros and modified to fit, but unsurprisingly it doesn't work in Excel ("ppPastePNG - variable not defined").
Sub PasteGraph1()
' PasteGraph Macro
Dim oGraph As Shape
Dim oGraphPic As Shape
Dim dGrpLeft As Double
Dim dGrpTop As Double
oGraph = ActiveSheet.ChartObjects("Chart 3").Copy
dGrpLeft = oGraph.Left
dGrpTop = oGraph.Top
oGraph.Copy
ActiveSheet.Shapes.PasteSpecial DataType:=ppPastePNG
Set oGraphPic = ActiveSheet.Shapes(ActiveSheet.Shapes.Count)
oGraph.Delete
oGraphPic.Left = dGrpLeft
oGraphPic.Top = dGrpTop
End Sub
The latter (PasteGraph1) seems to suit my purposes better, but how do I make it work? Is there a simpler way?
ppPastePng is a vba Variable for PowerPoint, so it is not defined in VBA for Excel.
This should work :
ActiveSheet.ChartObjects("Chart 3").Chart.CopyPicture xlScreen, xlBitmap
ActiveSheet.Paste
(Added as answer for completeness, accepted #ZwoRmi's answer because it seems churlish not to given that his suggestion proved vital to making it work...)
Many thanks to #ZwoRmi for the key to solving this - here is the code I ended up using, which is a combination and tweak of the original PasteGraph1 approach and #ZwoRmi's much more useful copy method.
Sub PasteGraph1()
' Converts live graph to static image
Dim oGraphPic As Shape
Dim dGrpLeft As Double
Dim dGrpTop As Double
dGrpLeft = ActiveSheet.ChartObjects("Chart 1").Left
dGrpTop = ActiveSheet.ChartObjects("Chart 1").Top
ActiveSheet.ChartObjects("Chart 1").Chart.CopyPicture xlScreen, xlBitmap
ActiveSheet.Paste
ActiveSheet.ChartObjects("Chart 1").Delete
Set oGraphPic = ActiveSheet.Shapes(ActiveSheet.Shapes.Count)
oGraphPic.Left = dGrpLeft
oGraphPic.Top = dGrpTop
End Sub

Unable to assign formula to cell range in Excel

Someone else's code in the project, that I am trying to fix up.
listO.Range(i, j).FormulaR1C1 = FormulaMatrix(i, j)
where FormulaMatrix(i, j) is always a String value. Whatever random/test value, I try with, is being assigned successfully, except when it is a formula, eg.
=IF(LENGTH([#Units])>0;[#SalesAmount]-[#DiscountAmount]0)
If I remove the = sign in the beginning of the formula, it gets assigned correctly, but then it's useless, because it's not a formula.
#Units, #SalesAmount, #DiscountAmount are references/names of columns.
So, when assigning a formula, I get an exception HRESULT: 0x800A03EC. I looked up in this answer in order to get explanation and followed some of the instructions there. I determined that my problem is the following: the problem happens due to a function entered in a cell and it is trying to update another cell.
Checked out also this post. I tried quite different (like putting just the formulas without = and then run over again and put the equal signs), but same problem.
I am clueless of how to approach this.
.formulalocalworks! (While .formula, .value and .formular1c1 don't.)
I've just started working with VB.NET and came into a very similar issue. This was my simplified data at first (Table1 in Sheet1):
Then after applying the code below I had this:
The whole code for the form:
Imports Excel = Microsoft.Office.Interop.Excel
Public Class Form1
'~~> Define your Excel Objects
Dim xlApp As New Excel.Application
Dim xlWorkBook As Excel.Workbook
Dim xlWorkSheet As Excel.Worksheet
Dim strAddress As String = "C:\Temp\SampleNew.xlsx"
Dim list1 As Object
Private Sub btnOpen_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOpen.Click
'~~> Add a New Workbook (IGNORING THE TWO DOT RULE)
xlWorkBook = xlApp.Workbooks.Open(strAddress)
'~~> Display Excel
xlApp.Visible = True
'~~> Set the relevant sheet that we want to work with
xlWorkSheet = xlWorkBook.Sheets("Sheet1")
With xlWorkSheet
'~~> Change the range into a tabular format
list1 = .ListObjects("Table1")
End With
list1.range(2, 4).formulalocal = "=IF(LEN([#Month])>5;[#Income]-[#MoneySpent];0)"
'~~> Save the file
xlApp.DisplayAlerts = False
xlWorkBook.SaveAs(Filename:=strAddress, FileFormat:=51)
xlApp.DisplayAlerts = True
'~~> Close the File
xlWorkBook.Close()
'~~> Quit the Excel Application
xlApp.Quit()
'~~> Clean Up
releaseObject(xlApp)
releaseObject(xlWorkBook)
End Sub
'~~> Release the objects
Private Sub releaseObject(ByVal obj As Object)
Try
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
obj = Nothing
Catch ex As Exception
obj = Nothing
Finally
GC.Collect()
End Try
End Sub
End Class
#Siddharth Rout helped a lot to build this code, as he owns this awesome site: http://www.siddharthrout.com/
The error might be coming from your current data, respectively, the layout of the sheet. I would suggest you to check what is inside the listO.Range(i, j).FormulaR1C1 before you assign the formula.
I have had a case where the range has already got wrong data inside, and then strangely, I don't know why, I cannot assign the new formula.
If that is the case - try clearing the value of the range and then assigning the formula:
listO.Range(i, j).FormulaR1C1 = ""
listO.Range(i, j).FormulaR1C1 = FormulaMatrix(i, j)
The problem might be with your formula. Try this-
=IF(LEN([#Units])>0,[#SalesAmount]-[#DiscountAmount],0)
If this doesn't work, I would try using the .formula method instead of .formulaR1C1.. Is there any reason in particular you are using R1C1 references?