I cannot access .Count property of Sheets. I'm using Excel Interop. I'm in debug mode and I'm trying this:
?xlSheets.Count
This results in:
(1) : error BC30456: 'Count' is not a member of 'Sheets'.
I have no clue on what's wrong, as I see in MSDN that there is such property!
This works well: ?xlSheets(1).Name. But Count fails... Is it possible to get the count of sheets?
These guys had a similar problem - they wanted to .Worksheets.Add(.Worksheets.Sheets.Count). Finally they did not get the count, they went for .Worksheets.Add(After:=.Worksheets(3))...
UPDATE:
To my great delight, after further trying / experimentations, it became clear that in debug modeSheets.Count does not work only when there is no such line in the code.
While debugging this code, I can access Sheets.Count, because this line exists in the code.
Imports Excel = Microsoft.Office.Interop.Excel
Public Class Form1
Dim xlApp As Excel.Application
Dim xlWorkBook As Excel.Workbook
Dim xlWorkbooks As Excel.Workbooks
Dim xlSheets As Excel.Sheets
Private Sub btnCreate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCreate.Click
xlApp = New Excel.Application
xlWorkbooks = xlApp.Workbooks
xlWorkBook = xlWorkbooks.Open("C:\Temp\Template.xlsm")
xlSheets = xlWorkBook.Sheets
MessageBox.Show(xlSheets.Count)
xlWorkBook.Close()
xlApp.Quit()
'Clean Up
releaseObject(xlSheets)
releaseObject(xlWorkBook)
releaseObject(xlWorkbooks)
releaseObject(xlApp)
End Sub
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()
GC.WaitForPendingFinalizers()
End Try
End Sub
End Class
But when I replace MessageBox.Show(xlSheets.Count) with MessageBox.Show(xlSheets.Creator), the error appears when trying to ?xlSheets.Count. I don't yet know the reason of such behaviour (I come from VBA environment where debug mode seems to be more flexible), but at least that works during run time...
If someone knows how to fix this, please let me know, as I feel restricted while testing small things in debug mode!
Use Project > Properties > References. Locate and select the "Microsoft Excel xx.x Object Library" entry. In the Properties window, set the Embed Interop Types property to False. Use Build > Rebuild to rebuild your app. It will now work the way you expected.
Briefly, this option is a strong optimization for COM interop libraries, like Microsoft.Office.Interop.Excel, you no longer have a runtime dependency on the library. The compiler copies the interop types from the library into your program's executable, only the ones you actually need to run your program. Explains your discovery, the Count property is in fact missing when you don't use it in your program.
You don't want to leave it this way, set the property back to True after you're done testing.
Your code works fine for me. I notice the file type is a macro-enabled workbook. Have you set your macro settings properly on your dev PC? By default Excel will disable macros.
Edit: I think I get your problem now. You are getting the error when trying to print the property in debug mode. Probably you have stopped the code at a point where the variable is not set (.Count is only available while the button code is actually running). Put a breakpoint on the message box line, click the button, and try again.
Related
I have just started to migrate some code from VBA to VB.Net. So I am an absolute beginner in VB.Net – but I want to do things right. Maybe some of my questions are stupid but I guess that is because I am a beginner.
So as a first exercise I have developed my first piece of code (see below). Now I thought I have to release ALL COM objects again. Two of them throw errors already while writing the code. And others throw errors at runtime.
But the funny thing is: Weather I release the rest of the COM objects or not (by making the relevant not yet commented lines of Marshal.Release to comments as well – then all lines starting with Marshal.Release are comment lines) the behavior of the code is absolutely the same to my eyes.
Can anybody tell me where I can see/find the difference?
The internet tells me that there must be a difference?
But I guess I just don’t understand (till now).
Besides this many more questions are in my head:
Does every “Dim” statement create a COM Object - that has to be released later on?
If not how do I detect whether a COM object has been created or not? Which “Dim” statements create COM object and which don't?
In this example: Dim ActiveWindow As Object = Nothing Try ActiveWindow = Me.HostApplication.ActiveWindow() Catch End Try
Is
Marshal.ReleaseComObject(ActiveWindow)
identical to
Marshal.ReleaseComObject(Me.HostApplication.ActiveWindow())?
According to this:
http://www.codeproject.com/Tips/235230/Proper-Way-of-Releasing-COM-Objects-in-NET
Would it not be better to release each "level" separately like this:
Marshal.ReleaseComObject(Me.HostApplication.ActiveWindow())
Marshal.ReleaseComObject(Me.HostApplication)
Marshal.ReleaseComObject(Me)
Overall: Am I trying to release too much? Or is it correct / good practie?
And what does "GC.Collect()" and "… = Null" have to do with all this? I have not used it at all. Should I better use it? Why? ( "... = Null" I have seen here:
http://www.codeproject.com/Tips/162691/Proper-Way-of-Releasing-COM-Objects-in-NET)
Why do I get “ShapeCount was not declared …” - Error if I try to do “Marshal.ReleaseComObject(ShapeCount)”? The same with “ShRange”. I think these are COM objects as well?!?
How do I notice when is the best time to release the COM object again? When I process/debug my code step by step with F11 will it be possible for me to determine the best (soonest) point of release? So far I have no “feeling” about when the COM object is not needed anymore and I can release it.
Any help and explanations very welcome.
Here is the code I am talking about:
Imports System.Runtime.InteropServices
Imports System.ComponentModel
Imports System.Windows.Forms
Imports AddinExpress.MSO
Imports PowerPoint = Microsoft.Office.Interop.PowerPoint
'Add-in Express Add-in Module
<GuidAttribute("D75C609E-7632-400F-8A6F-6A6E6E744E75"),
ProgIdAttribute("MyAddin8.AddinModule")> _
Public Class AddinModule
Inherits AddinExpress.MSO.ADXAddinModule
#Region " Add-in Express automatic code "
[…]
#End Region
Public Shared Shadows ReadOnly Property CurrentInstance() As AddinModule
Get
Return CType(AddinExpress.MSO.ADXAddinModule.CurrentInstance, AddinModule)
End Get
End Property
Public ReadOnly Property PowerPointApp() As PowerPoint._Application
Get
Return CType(HostApplication, PowerPoint._Application)
End Get
End Property
Private Sub AdxRibbonButton2_OnClick(sender As Object, control As IRibbonControl, pressed As Boolean) Handles AdxRibbonButton2.OnClick
MsgBox(GetInfoString2())
End Sub
Friend Function GetInfoString2() As String
Dim ActiveWindow As Object = Nothing
Try
ActiveWindow = Me.HostApplication.ActiveWindow()
Catch
End Try
Dim Result As String = "No document window found!"
If Not ActiveWindow Is Nothing Then
Select Case Me.HostType
Case ADXOfficeHostApp.ohaPowerPoint
Dim Selection As PowerPoint.Selection =
CType(ActiveWindow, PowerPoint.DocumentWindow).Selection
Dim WindowViewType As PowerPoint.PpViewType = PowerPoint.PpViewType.ppViewNormal
Dim SlideRange As PowerPoint.SlideRange = Selection.SlideRange
Dim SlideCountString = SlideRange.Count.ToString()
If WindowViewType = 9 And SlideCountString < 2 Then
Dim ShRange As PowerPoint.ShapeRange = Nothing
Try
ShRange = Selection.ShapeRange
Catch
End Try
If Not ShRange Is Nothing Then
Dim ShapeCount = ShRange.Count.ToString()
Result = "You have " + ShapeCount _
+ " shapes selected."
Else
Result = "You have 0 shapes selected."
End If
End If
'Marshal.ReleaseComObject(ShapeCount)
'Marshal.ReleaseComObject(ShRange)
'Marshal.ReleaseComObject(WindowViewType)
'Marshal.ReleaseComObject(SlideCountString)
Marshal.ReleaseComObject(SlideRange)
Marshal.ReleaseComObject(Selection)
Case Else
Result = AddinName + " doesn't support " + HostName
End Select
'Marshal.ReleaseComObject(Me.HostType)
'Marshal.ReleaseComObject(Result)
Marshal.ReleaseComObject(Me.HostApplication.ActiveWindow())
Marshal.ReleaseComObject(Me.HostApplication)
'Marshal.ReleaseComObject(Me)
End If
Return Result
End Function
End Class
The ReleaseComObject method of the Marshal class decrements the reference count of the specified Runtime Callable Wrapper (RCW) associated with the specified COM object, it doesn't release an object. It comes from the COM nature.
Typically you need to release every object returned from the Office (PowerPoint in your case) object model. Exceptions are objects passed to event handlers as parameters.
You may read more about that and find answers to your multiple questions in the When to release COM objects in Office add-ins developed in .NET article.
FinalReleaseComObject calls ReleaseComObject til it returns 0 which means release of COM object. Calling them in reverse order as in Excel objects(Application, Workbook, Worksheet) is the proper way to dispose of COM objects that are related.
Exception Condition
ArgumentException
o is not a valid COM object.
ArgumentNullException
o is null.
I am hoping you can help me here, in the past you all have been great. I have tried every variation of the kill script for killing excel from vb.net, to no avail.
First I can't post explicit code on here because it is my company's proprietary software, but I can tell you a few things. Also there are over 28,000 lines of code.
I am not using
Imports Excel = Microsoft.Office.Interop.Excel
due to the fact that we have to accommodate different variations of clients software. I am creating the new excel as an object as such
Dim XLObj As Object = CreateObject("Excel.Application")
I have seen this used on several other sites but the kill function they are using is when you save and then close it, which I'm not doing.
The error message I am getting says that "Com object that has been separated from its underlying RCW cannot be used". I'm not sure where this com object is because I have released the sheets, workbook and then the application.
Oh and I don't want to use the excel.kill() because if a client already has the excel open I don't want to kill it without saving it. I only want to kill the newly generated excel process that doesn't have a window open associated with it.
My questions are as follows
I need to be able to close the Excel application when/if the open fails. So say I am click a link and it opens the dialog box to select an Excel template to load but either the data from the database is corrupt or the sql statement is broken. The program throws and error and then Excel should close in the Task Manager. Unfortunately it doesn't close hence the problem.
is there a way to close only the newly created process id? I have tried to use the directions here but it doesn't work either. When I do that it gives me a different error "Value cannot be null Parameter name: o". The line that is throwing the error is on (from the link)
Marshal.FinalReleaseComObject(tempVar)
I only tried this because we are using the With on the XLObj. The With is in reference to the workbook itself so shouldn't it be released when I close the workbook? And being as I'm causing it to error on purpose at the moment it shouldn't reach the With statement anyway.
Is there a way to tell which com object is not closing?
Things I have tried:
This releaseObject that I found on the internet. (don't ask me where I've been through about 75 pages)
Private Sub releaseObject(ByRef obj As Object)
Try
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(obj)
If obj Is Nothing Then
Else
obj = Nothing
End If
Catch ex As Exception
If obj Is Nothing Then
Else
obj = Nothing
End If
Finally
GC.Collect()
GC.WaitForPendingFinalizers()
End Try
End Sub
This is used in conjunction with this function (which was pieced together from the many sites I have been on)
Public Sub CloseExcel(ByRef WorkBook As Object, ByRef Application As Object)
Dim xLSheet As Object = WorkBook.Sheets
For Each xLSheet In WorkBook.Sheets
If xLSheet IsNot Nothing Then
releaseObject(xLSheet)
End If
If xLSheet IsNot Nothing Then
Kill(xLSheet)
End If
Next
If WorkBook IsNot Nothing Then
WorkBook.Close(False)
End If
If WorkBook IsNot Nothing Then
Kill(WorkBook)
End If
releaseObject(WorkBook)
If Application IsNot Nothing Then
Application.Quit()
End If
If Application IsNot Nothing Then
Kill(Application)
End If
releaseObject(Application)
GC.Collect()
GC.WaitForPendingFinalizers()
Application.Quit()
End Sub
and because it is also referenced the Kill function
Public Sub Kill(ByRef obj As Object)
Try
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(obj)
Catch ex As Exception
MessageBox.Show("moduleExcel.Kill " & ex.Message)
Finally
obj = Nothing
End Try
End Sub
any help would be greatly appreciated.
Ok so for those of you having this exact same issue. I do have a solution for you. Yes the above code does work but for a few minor adjustments.
you need to take out all the code in the CloseExcel sub and place it EXACTLY where you want it to close. So if you want it to close if the program errors out, put after the catch statement. You cannot call a Sub and pass in your objects and expect it to kill the process.
you need a few bits above the opening of the new Excel process. and they are as follows.
'declare process for excel
Dim XLProc As Process
'loads the financials excel bookmarks
'this will be where you declare your new excel opbject
Dim XLObj As Object = CreateObject("Excel.Application")
'get window handle
Dim xlHWND As Integer = XLObj.hwnd
Dim ProcIDXL As Integer = 0
'get the process ID
GetWindowThreadProcessId(xlHWND, ProcIDXL)
XLProc = Process.GetProcessById(ProcIDXL)
and of course you will need the GetWindowThreadProcessId which I got from the link I included in the original question. I am posting it here so you don't have to search for it.
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Private Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer
End Function
This code will only close the single process you have it associated with, it will not close other open Excel files. Our clients sometimes will have multiple files open and we don't want to close them without telling them. This KILLS the Excel process that was created at run time when the system Errors out.
I am using VB.NET to open the Excel files but dont want to create excel object every time.
My code is working perfectly in debug mode, but after publish, it never gets the existing instances and always create new instances which we can see from Task Manager. Here is my code which always returns false in published mode.
My OS is Windows Server 2008. Please guide how to solve this.
Function IsExcelRunning() As Boolean
Dim xlApp As Excel.Application
On Error Resume Next
xlApp = GetObject(, "Excel.Application")
IsExcelRunning = (Err.Number = 0)
MyHelper.writeLog("Excel Instance found=" & IsExcelRunning)
xlApp = Nothing
Err.Clear()
End Function
Here is how I call.
If IsExcelRunning() Then
excelApp = GetObject(, "Excel.Application")
Else
excelApp = Server.CreateObject("Excel.Application")
End If
We used to use Excel Interop and I remember it always being difficult to work with (clunky.) Due to the Interop opening an Excel process and not closing it every time you use it, makes it difficult to work with.
The Interop opens Excel automatically, so all we needed to do was close it. This is what we used to use to kill the Process. Replace YourProcessName with Excel.exe.
Dim proc As System.Diagnostics.Process
Dim info As ManagementObject
Dim search As New ManagementObjectSearcher("SELECT ProcessId FROM Win32_process WHERE caption = 'YourProcessName'")
For Each info In search.Get()
Dim TheString As String = info.GetText(TextFormat.Mof).ToString
proc = System.Diagnostics.Process.GetProcessById(Mid$(TheString, _
(Len(TheString) - 8), 4))
proc.CloseMainWindow()
proc.Refresh()
If proc.HasExited Then GoTo NoKill
proc.Kill()
NoKill:
Next
You'll need to import
Imports System.Management
You'll also need to add the reference 'System.Management' to your project.
See: http://msdn.microsoft.com/en-us/library/vstudio/wkze6zky.aspx for adding a reference to a project.
If you can rather work with CSV, I would suggest you try to. If you are creating the Excel file yourself, try to find a report creator that let's you create / export to Excel. You'll save yourself a lot of time in the long run.
I'm trying to write an application in VB.net that assembles an Excel Workbook by wisely coping cells from another opened Workbook. [Note: as for now, the two workbooks are opened within the same Excel application - Originally I was using two different Excel instances, but only later I realized that the PasteSpecial between two Instances behaves differently]
I'm using Visual Studio 2012, Excel 2007 and I'm including Microsoft Excel 12.0 Object Library in the project references
The code is something like that:
Dim appXL As Excel.Application
Dim wbXLsource As Excel.Workbook
Dim wbXLtarget As Excel.Workbook
''with two different buttonclick event handlers
''I assign wbXLsource and wbXLtarget
''the full code is omitted
...
wbXLsource = appXL.Workbooks.Open(strFileNameAndPath)
...
...
wbXLtarget = appXL.Workbooks.Add
...
''I use a third button handler for the
''Copy and PasteSpecial Operations
Private Sub btnAppendWorksheet_Click(sender As Object, e As EventArgs) _
Handles btnAppendWorksheet.Click
Dim shXLtar As Excel.Worksheet
Dim shXLsou As Excel.Worksheet
shXLtar = wbXLtarget.ActiveSheet
shXLtar.Cells.Clear()
shXLsou = wbXLsource.ActiveSheet
shXLsou.Range("A1:H433").Copy()
Try
shXLtar.Range("A1:H433").PasteSpecial(Excel.XlPasteType.xlPasteAll, False, False) ''Paste special Format:=
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
The PasteSpecial method throws the exception "PasteSpecial method of Range class failed".
What is strange is that the same code originally worked within two workbooks that run in different Excel instances [At that time I had appXLtarget and appXLsource].
Needless to say that I tried all the possible combinations of "Selection", "Activate" in any part of the code: eg between Copy and PasteSpecial etc etc.
Probably there is something really coarse that I'm missing <- I'm new of VB.net
Thanks for any help and Best Regards!
If you are new in VB.Net, you should first do research about OptionStrict. With optionStrict set to ON, VS won't compile your code...
Replace
shXLtar.Range("A1:H433").PasteSpecial(Excel.XlPasteType.xlPasteAll, False, False)
With
shXLtar.Range("A1:H433").PasteSpecial(Excel.XlPasteType.xlPasteAll,Excel.XlPasteSpecialOperation.xlPasteSpecialOperationNone,False, False)
or
shXLtar.Range("A1:H433").PasteSpecial(Excel.XlPasteType.xlPasteAll)
Hope this helps.
I wrote a vb.net program using Visual Studio 2010 Express to open and manipulate an Excel workbook. I have MS Office 2010 and my program works fine on my computer. When I try to run it on my wife's computer... nothing. I get no error messages... nothing. She has the same version of Office as I do, but she has Windows XP and I have Vista and her computer is has more security protocols than mine, but my other program, which does not import any Office namespaces, runs fine on her computer.
When I look at the task manager on her computer there is nothing under the Applications tab, but under the Processes tab I see an instance of Excel.exe, and if I try to run the program again, still nothing under applications, but yet another instance of Excel.exe under processes.
I have tried to install my program using the Click-Once technology and just the Release folder after a Rebuild... nothing.
I have also tried to compile it under versions 2.0, 3.0, etc.... nothing.
Here is some of the relevant code:
Public Class CleanUpDataForm
Dim m_objExcel As New Excel.Application
Dim m_rngRange, m_rngEnd As Excel.Range
Dim m_blnEntireRow, m_blnCancel As Boolean
Dim m_strRange, m_strSearch, m_strLogFrameCode As String
Const c_strCode As String = "log"
Friend Const c_intScrollBarWidth As Integer = 30
Private Sub CleanUpDataForm_FormClosed(sender As Object, e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
m_objExcel = Nothing
Dispose()
End Sub
Private Sub tsbOpen_Click(sender As System.Object, e As System.EventArgs) Handles tsbOpen.Click
ofdGetWorksheet.Title = "Select Worksheet"
ofdGetWorksheet.Filter = "All Excel Files (*.xl*)|*.xl*|(*.xlsx)|*.xlsx|(*.xlsm)|*.xlsm|(*.xlsb)|*.xlsb|(*.xlam)|*.xlam|(*.xltx)|*.xltx|(*.xltm)|*.xltm|(*.xls)|*.xls|(*.xla)|*.xla|(*.xlt)|*.xlt|(*.xlm)|*.xlm|(*.xlw)|*.xlw"
ofdGetWorksheet.FileName = ""
If ofdGetWorksheet.ShowDialog <> System.Windows.Forms.DialogResult.Cancel Then
Me.Text = ofdGetWorksheet.FileName
m_objExcel.Workbooks.Open(Me.Text)
tsbCopySheet.Enabled = True
tsbFindRows.Enabled = True
tsbClose.Enabled = True
m_objExcel.Visible = True
Else
Me.Text = ""
End If
Me.Activate()
End Sub
Any ideas??
I believe that I was having this issue as well, but this line of code (after your m_objExcel.visible statement) was what fixed it (going from memory).
m_objExcel.Windows(1).Visible = True
One thing you may want to look at is the Marshal.BindToMoniker method in the InteropServices library. I found this incredibly useful. If the file is not open, then it opens it. If it is open already, then it will use that instance. Like this...
wb = System.Runtime.InteropServices.Marshal.BindToMoniker(fileName) 'open the file. If the file is already open, then this uses the open file instead of trying to open again
exApp = wb.Parent
wb is my workbook variable, exApp is my Excel variable.
One other thing; to make sure Excel is closed and to remove those pesky processes from Task Manager
Private Sub KillExcel()
For Each RunningProcess In Process.GetProcessesByName("Excel")
RunningProcess.Kill()
Next
End Sub