VB.NET: Excel Crashes when Updating Data in a Word Chart - excel

Update: Releasing objects has no effect on Excel crashing. The problematic line is:
Dim wChartData = wChart.ChartData
I have written a VB.Net application that opens a word document, iterates through the inline shapes, and updates each chart with data from the database. Sometimes Excel will crash when opening the sheet containing the chart data. Can anyone tell me how I fix this?
Here is the code that iterates through the shapes:
wApp = New Word.Application
wApp.Visible = True
wDoc = wApp.Documents.Add("Some_File_Name.docx")
Console.WriteLine("Updating Charts")
Dim chartName As String
For Each wShape As Word.InlineShape In wDoc.InlineShapes
If wShape.HasChart = Core.MsoTriState.msoTrue Then
If wShape.Chart.HasTitle Then
chartName = wShape.Chart.ChartTitle.Text
Else
chartName = "NO_TITLE"
End If
UpdateChart(wShape.Chart, reportID, reportTitle,
reportUser, curriculumYear, chartName)
End If
Next
The UpdateChart subroutine grabs a SQL query and some options related to the chart, then fires off the FillChartData subroutine below:
Public Sub FillChartData(ByRef wChart As Word.Chart, ByVal sql As String,
Optional ByVal addDataPointsToLabels As Boolean = False)
If sql = "" Then Exit Sub
Dim cmd = O.factory.CreateCommand()
cmd.CommandText = sql
cmd.Connection = O.con
O.factory.CreateDataAdapter()
Dim adapter = O.factory.CreateDataAdapter
adapter.SelectCommand = cmd
Dim dt As New System.Data.DataTable()
Dim ds As New System.Data.DataSet()
adapter.Fill(ds, "report_name")
dt = ds.Tables(0)
Dim wChartData = wChart.ChartData
Dim wChartWb As Excel.Workbook = wChartData.Workbook
Dim wChartSheet As Excel.Worksheet = wChartWb.Sheets(1)
Dim title As String = "No title"
If wChart.HasTitle Then title = wChart.ChartTitle.Text.ToString
Dim r As Excel.Range
r = wChartSheet.Range("A1")
r.CurrentRegion.Clear()
For i = 0 To dt.Columns.Count - 1
Dim c As System.Data.DataColumn = dt.Columns(i)
r.Offset(0, i).Value2 = c.ColumnName
Next
r = wChartSheet.Range("A2")
For Each row As System.Data.DataRow In dt.Rows
For i = 0 To row.ItemArray.Count - 1
r.Offset(0, i).Value2 = row.Item(i)
Next
r = r.Offset(1)
Next
r = wChartSheet.Range("A1")
If addDataPointsToLabels Then
While r.Value <> ""
r.Value &= " " & r.Offset(1).Value
r = r.Offset(0, 1)
End While
End If
wChartWb.Close()
releaseObject(r)
releaseObject(wChartSheet)
releaseObject(wChartWb)
releaseObject(wChartData)
r = Nothing
wChartSheet = Nothing
wChartWb = Nothing
wChartData = Nothing
GC.Collect()
End Sub
The releaseObject subroutine is as follows:
Private Sub releaseObject(ByVal obj As Object)
Try
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
obj = Nothing
Catch ex As Exception
MessageBox.Show(ex.ToString)
obj = Nothing
End Try
End Sub
And here's the crash report:
Problem signature:
Problem Event Name: APPCRASH
Application Name: EXCEL.EXE
Application Version: 15.0.5007.1000
Application Timestamp: 5a5eb36d
Fault Module Name: EXCEL.EXE
Fault Module Version: 15.0.5007.1000
Fault Module Timestamp: 5a5eb36d
Exception Code: c0000005
Exception Offset: 002b71c8
OS Version: 6.1.7601.2.1.0.256.4
Locale ID: 1033
Additional information about the problem:
LCID: 1033
skulcid: 1033
Read our privacy statement online:
http://go.microsoft.com/fwlink/?linkid=104288&clcid=0x0409
If the online privacy statement is not available, please read our privacy statement offline:
C:\Windows\system32\en-US\erofflps.txt
Thanks for your help!

You need to Activate the Word ChartData object to begin the inter-process communication between Word and Excel.
The example below is a simplified demonstration of code pattern and contains no error handling. This example also demonstrates releasing out of scope COM objects via the garbage collector. See this answer for more discussion on this COM clean-up procedure.
This code was verified against Office 2007.
Imports System.Runtime.InteropServices
Imports Excel = Microsoft.Office.Interop.Excel
Imports Word = Microsoft.Office.Interop.Word
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
InterOpWork("Embedded Excel Chart.docx")
COMCleanup()
End Sub
Sub InterOpWork(filePath As String)
Dim appWord As New Word.Application
Dim doc As Word.Document = appWord.Documents.Open((filePath))
Dim shp As Word.InlineShape = doc.InlineShapes(1)
Dim ch As Word.Chart = shp.Chart
Dim chData As Word.ChartData = ch.ChartData
chData.Activate() ' **** This is what your code is missing
Dim wb As Excel.Workbook = DirectCast(chData.Workbook, Excel.Workbook)
Dim appExcel As Excel.Application = DirectCast(wb.Application, Excel.Application)
Dim ws As Excel.Worksheet = DirectCast(wb.Worksheets("Sheet1"), Excel.Worksheet)
Dim rng As Excel.Range = ws.Range("B2:B4")
Dim dataToChange As Object(,) = DirectCast(rng.Value2, Object(,))
For i As Int32 = dataToChange.GetLowerBound(0) To dataToChange.GetUpperBound(0)
dataToChange(i, 1) = i * 2 + (5 - i)
Next
rng.Value = dataToChange
wb.Save()
wb.Close(False)
appExcel.Quit()
doc.Save()
doc.Close(False)
appWord.Quit()
End Sub
Private Sub COMCleanup()
Do
GC.Collect()
GC.WaitForPendingFinalizers()
Loop While Marshal.AreComObjectsAvailableForCleanup
End Sub
End Class

Related

Copy and paste picture from excel to word

I am trying to try another method that is not to export the images from excel and then import them to word.
This method makes use of copy and paste, however I have encountered a problem using different versions of Office. In some it pastes it as InlineShape and in another as Shape.
I don't know how to correctly reference a variable in the pasted image. I thought I could use something like set object = selection after pasting the image but it doesn't work.
The purpose of referencing it is to add a text that allows me to delete it if I insert an update of the same image.
For the inlineshape I have solved it using the InlineShape.Range.BookmarkID property but if it is a Shape object I don't know the way.
Could anyone help me?
Code:
Sub Copy_Paste_Image_Bookmark(sBookmark As String, sImage As String, Optional sSheet As String, Optional sWorkbook As String)
Dim xlApp As Excel.Application, xlWrk As Excel.Workbook, xlSht As Excel.Worksheet
Dim oShp As Excel.Shape
Set xlApp = GetObject(, "Excel.Application")
Set xlWrk = xlApp.Workbooks(sWorkbook)
Set xlSht = xlWrk.Worksheets(sSheet)
xlSht.Shapes(sImage).Copy
'Control for word
Dim docWord As Word.Document
Dim oBookmark As Bookmark, rBookmark As Word.Range, oInLiShp As Word.InlineShape
Dim lInLiShapes As Long, idx As Long, lInLiShapes_old As Long
Dim lShapes As Long, lShapes_old As Long, bIsInlineShape As Boolean, bIsShape As Boolean
Dim oShape As Word.Shape, oShapes As Word.Shapes
Set docWord = ThisDocument
'If exists bookmark
If docWord.Bookmarks.Exists(sBookmark) Then
Set oBookmark = docWord.Bookmarks(sBookmark)
Set rBookmark = oBookmark.Range
'Delete previous text
'rBookmark.MoveEndUntil Chr(46), wdForward 'chr(12) jump page
rBookmark.Expand Unit:=wdParagraph
rBookmark.MoveEnd Unit:=wdCharacter, Count:=-1
If StrComp(rBookmark.Text, "Text test") = 0 Then rBookmark.Delete
'Delete previous image
idx = GetIndex_Inlishape_BookmarkID(oBookmark.Range.BookmarkID)
If idx > 0 Then docWord.InlineShapes(idx).Delete
'Recover count of shapes
lInLiShapes_old = docWord.InlineShapes.Count
lShapes_old = docWord.Shapes.Count
'Paste image
rBookmark.PasteAndFormat wdFormatOriginalFormatting
'Recover new count shapes
lInLiShapes = docWord.InlineShapes.Count
lShapes = docWord.Shapes.Count
'Determine type pasted shape
bIsInlineShape = lInLiShapes > lInLiShapes_old
bIsShape = lShapes > lShapes_old
'If is inlineshape
If bIsInlineShape And bIsShape = False Then
idx = GetIndex_Inlishape_BookmarkID(oBookmark.Range.BookmarkID)
Set oInLiShp = docWord.InlineShapes(idx)
ElseIf bIsShape And bIsInlineShape = False Then
Set oShape = docWord.Shapes(lShapes)
'Convert to inlineshape
Set oInLiShp = oShape.ConvertToInlineShape
Else
Exit Sub
End If
'Change some options
oInLiShp.Title = sImage
oInLiShp.Range.Paragraphs.Alignment = wdAlignParagraphCenter
Else
MsgBox "The bookmark " & sBookmark & " doesn't exist in the document.", vbOKOnly + vbCritical, "Not exists bookmark"
End If
End Sub
Function GetIndex_Inlishape_BookmarkID(bkm_ID As Long) As Long
Dim o As InlineShape, i As Long
For Each o In ThisDocument.InlineShapes
i = i + 1
If o.Range.BookmarkID = bkm_ID Then
Select Case o.Type
Case wdInlineShapePicture
GetIndex_Inlishape_BookmarkID = i
Exit Function
End Select
End If
Next
GetIndex_Inlishape_BookmarkID = 0
End Function
Solved with Set oShape = docWord.Shapes(sImage) because image pasted keep the name of shape from Excel although with .count of the collection Shapes run fine.
However with .count of the collection inlineshapes not run fine because Word orders the elements, first the shapepictures and after shapecharts.
Thanks.

Can't properly close Excel application

i am very new to VB.net and i'm trying to proceed step by step with my application.
The application i'm trying to build will collect a series of macros i've written in Excel VBA environment.
Now, the following code pasted below, is the initial part, where basically i try to load a workbook (to be used as Active workbook) and to "unload it".
The issue comes when, after "unloading" the workbook, i try to open the very same workbook in excel.
Excel application return an error that is "Open in read-only". This cannot be accepted, and i need to understand how to unload the workbook and release it from the myAPP.
Imports Excel = Microsoft.Office.Interop.Excel
Public Class Form1
Dim workbook As Excel.Workbook
Dim worksheet As Excel.Worksheet
Dim APP As New Excel.Application
Private Sub opn_btn_Click(sender As Object, e As EventArgs) Handles opn_btn.Click
Dim strname As String
Dim cellname As String
With OpenFileDialog1
.InitialDirectory = "E:\Vs_Excel"
.Title = "Open xlsx file"
.ShowDialog()
End With
workbook = APP.Workbooks.Open(OpenFileDialog1.FileName)
worksheet = workbook.Worksheets("sheet1")
cellname = worksheet.Range("A1").Value
strname = OpenFileDialog1.FileName
Me.TextBox1.Text = strname
Me.TextBox2.Text = cellname
Dim lvwReport As View = View.List
With Me.ListView1
.GridLines = True
.View = lvwReport
.CheckBoxes = True
End With
'LoadListView() 'thi sroutine is written but not used yet. Must solve first the problem wioth closing ExcelApplication
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
workbook.Close()
APP.Quit()
Me.TextBox1.Text = ""
Me.TextBox2.Text = ""
ReleaseObject(worksheet)
worksheet = Nothing
ReleaseObject(workbook)
workbook = Nothing
ReleaseObject(APP)
APP = Nothing
End Sub
Private Sub ReleaseObject(ByVal obj As Object)
Try
Dim intRel As Integer = 0
Do
intRel = System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
Loop While intRel > 0
MsgBox("Final Released obj # " & intRel)
Catch ex As Exception
MsgBox("Error releasing object" & ex.ToString)
obj = Nothing
Finally
GC.Collect()
End Try
End Sub
End class

Count lines (max) with values

I would like to count the lines that have values. I tried oSheet.Rows.Count but that doesn't work. Any idea about this?
My code is the following:
Dim oExcel As Object
Dim oBook As Object
Dim oSheet As Object
oExcel = CreateObject("Excel.Application")
oBook = oExcel.Workbooks.Add
oSheet = oBook.Worksheets("Sheet")
oSheet.Range("A" & max).Value = "0000111"
oSheet.Range("B1").Value ="Name"
oBook.SaveAs("C:\New folder\excel\" & datenw & ".xlsx")
oExcel.Quit()
As said in the comments, the following code should get you the count of rows that have values based on your Range:
Dim rowCount As Integer = oSheet.UsedRange.Rows.Count()
There is however a slight issue with your code I believe. This probably won't work:
oSheet = oBook.Worksheets("Sheet")
The reason it won't, is because "Sheet" doesn't exist on a new Workbook. "Sheet1" does, so this needs to be changed to:
oSheet = oBook.Worksheets("Sheet1")
'or
oSheet = oBook.Worksheets(1) 'remember Excel collections are one based not zero based
Lastly I would look at the way you are closing Excel as oExcel.Quit() is probably leaving an instance of Excel running. Have a look at this answer which links to Siddharth Rout's bit of code:
Private Sub ReleaseObject(ByVal obj As Object)
Try
Dim intRel As Integer = 0
Do
intRel = System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
Loop While intRel > 0
obj = Nothing
Catch ex As Exception
obj = Nothing
Finally
GC.Collect()
End Try
End Sub
You also to make sure you release in the right order and release everything. This is usually in backwards order:
ReleaseObject(oSheet)
oBook.Close()
ReleaseObject(oBook)
oExcel.Quit()
ReleaseObject(oExcel)
However with all that said I would look at using the Microsoft.Office.Interop.Excel namespace directly rather than declaring objects:
Imports Microsoft.Office.Interop
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim oExcel As New Excel.Application
Dim oWorkbooks As Excel.Workbooks = oExcel.Workbooks
Dim oWorkbook As Excel.Workbook = oWorkbooks.Add()
Dim oSheets As Excel.Sheets = CType(oWorkbook.Sheets, Excel.Sheets)
Dim oWorksheet As Excel.Worksheet = CType(oSheets(1), Excel.Worksheet)
Dim oARange As Excel.Range = oWorksheet.Range("A" & max.ToString()) 'Not sure what max is but I took the assumption it's an Integer
oARange.Value = "0000111"
Dim oBRange As Excel.Range = oWorksheet.Range("B1")
oBRange.Value = "Name"
Dim oUsedRange As Excel.Range = oWorksheet.UsedRange()
Dim rowCount As Integer = oUsedRange.Rows.Count()
oWorkbook.SaveAs("C:\Test.xlsx")
ReleaseObject(oUsedRange)
ReleaseObject(oBRange)
ReleaseObject(oARange)
ReleaseObject(oWorksheet)
ReleaseObject(oSheets)
oWorkbook.Close()
ReleaseObject(oWorkbook)
ReleaseObject(oWorkbooks)
oExcel.Quit()
ReleaseObject(oExcel)
End Sub
Private Sub ReleaseObject(ByVal obj As Object)
Try
Dim intRel As Integer = 0
Do
intRel = System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
Loop While intRel > 0
obj = Nothing
Catch ex As Exception
obj = Nothing
Finally
GC.Collect()
End Try
End Sub
End Class
I would also then look at turning Option Strict On:
Restricts implicit data type conversions to only widening conversions, disallows late binding, and disallows implicit typing that results in an Object type.
Define a row variable as Long, then start a loop which will end when it finds a blank value in column A:
Dim lRow as Long = 1
Do until oSheet.Range("A" & lRow).Value=""
' increment the loop variable
lRow+=1
Loop
' display the result in a message block
MsgBox(lRow-1)

Creating Map Point object in Excel VBA

This code has a run-time error saying object required on this line...
Set objDataSets = objApp.ActiveMap.DataSets
This is what I used as a reference...
http://msdn.microsoft.com/en-us/library/aa723407.aspx
Sub CreateMaps()
Dim MPApp As MapPoint.Application
Set MPApp = New MapPoint.Application
MPApp.Visible = True
MPApp.UserControl = True
OpenDataSet
End Sub
Sub OpenDataSet()
Dim objDataSets As MapPoint.DataSets
Dim objDataSet As MapPoint.DataSet
Dim zDataSource As String
zDataSource = "S:\Projects\StateMapData.xlsx!Data!AY5:AZ56"
Set objDataSets = objApp.ActiveMap.DataSets
Set objDataSet = objDataSets.ImportData(zDataSource)
End Sub

looping read excel data

I need to loop the read data from excel to vb.net and when I reach the last row/column "!##$%^&*()" the excel data will stop read. How can I do that?
Imports Excel = Microsoft.Office.Interop.Excel
Public Class Form1
Dim xlApp As Excel.Application
Dim xlWorkBook As Excel.Workbook
Dim xlWorkSheet As Excel.Worksheet
Dim xRange As Excel.Range
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
End Sub
Private Sub cmdGenerate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdGenerate.Click
'Dim row As String
Dim empty_cell_ctr As Integer = 0 '5
Dim end_of_xlsheet As Boolean = False
Dim sRow As Integer 'start row
Dim col_end As Integer 'col W
'
'loading excel(open and read)
xlApp = New Excel.ApplicationClass
xlWorkBook = xlApp.Workbooks.Open("c:\sample.xls")
xlWorkSheet = xlWorkBook.Worksheets("Timesheet")
xlApp.Visible = True
While Not end_of_xlsheet
If sRow = "'!##$%^&*()_+" Then
xRange = xRange.Cells(sRow, col_end)
end_of_xlsheet = False 'end of sheet
Continue While
End If
sRow += 1
End While
MessageBox.Show(sRow)
End Sub
End Class
You seem to be overthinking this, you can get all of the data in the spreadsheet by accessing the UsedRange property, and then load this into a 2D array variable by accessing the Value2 property of this object like so:
Dim application = New Excel.Application()
Dim workbook As Excel.Workbook = application.Workbooks.Open("C:\aaa\bbb.xlsx")
Dim worksheet As Excel.Worksheet = workbook.Sheets(1)
Dim usedRange = worksheet.UsedRange
Dim usedRangeAs2DArray As Object(,) = usedRange.Value2
workbook.Save()
workbook.Close()
application.Quit()
Marshal.ReleaseComObject(application)
You may use LIKE operator :)
'Not sure why you are trying to check Row number for set of characters...
Excel.Range range = sheet.UsedRange;
Int rows_count = range.Rows.Count;
For (int sRow = 1; sRow <= rows_count; sRow++)
If (sheet.Cells[sRow, col_end].value Like "*'!##$%^&*") Then
'-- input the data to where ever you want. It is best to store it into an array first..
xRange = sheet.Cells[i, col_end].value;
Break;
Else
sRow++;
End If
Please take a look the following reference for better understanding your options.
import data from Excel to VB.Net
read data from Excel to VB.Net

Resources