vb.net "PasteSpecial method of Range class failed" - vb.net

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.

Related

Getting the count of sheets in debug mode - MS Excel PIA

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.

Call a solidworks macro from a sub and pass global variables

In Solidworks I record two macros.
Macro 1 is empty:
Dim swApp As Object
Dim Part As Object
Dim boolstatus As Boolean
Dim longstatus As Long, longwarnings As Long
'added code
Dim distance_of_second_plane
'end of added code
Sub main()
Set swApp = _
Application.SldWorks
Set Part = swApp.ActiveDoc
'added code: here I want to call the second macro and send it distance_of_second_plane, and have it use that value
distance_of_second_plane = .05
'.. now what?
'end of added code, don't know what to add.
End Sub
Macro 2 does something that requires data from macro 1:
Dim swApp As Object
Dim Part As Object
Dim boolstatus As Boolean
Dim longstatus As Long, longwarnings As Long
Sub main()
Set swApp = _
Application.SldWorks
Set Part = swApp.ActiveDoc
boolstatus = Part.Extension.SelectByID2("Front Plane", "PLANE", 0, 0, 0, True, 0, Nothing, 0)
Dim myRefPlane As Object
Set myRefPlane = Part.FeatureManager.InsertRefPlane(8, 0.05334, 0, 0, 0, 0)
Part.ClearSelection2 True
End Sub
these macros are of course saved in different files. How do I call the second from the first, passing in the data from the first, and using it in the second?
things I`ve tried: http://support.microsoft.com/kb/140033, http://www.cadsharp.com/macros/run-macro-from-another-macro-vba/, VBA module that runs other modules, Call a Subroutine from a different Module in VBA
all of them are problematic. I'll give details of the errors I get if asked for them.
You could create a separate module to encapsulate your common methods. Then add reference to that module and call it from both your Macro1 and Macro2 sub routines.
So for example in your Macro1:
In the project explorer, right click on the Modules folder and Insert-Module, name in Module1 (or whatever you want)
Create a subroutine in Module1, name it InsertPlane (or whatever name makes sense), and construct the subroutine with parameters that will be needed to accomplish what needs to be done
Sub InsertPlane(ByRef swApp As Object, ByVal distance As Double)
'enter your code here, the parameters passed to InsertPlane can be whatever you need
'to do what the method needs to do.
End Sub
In your main() method of your macro, call the InsertPlane() method when you need it
Sub main()
Set swApp = _
Application.SldWorks
Set Part = swApp.ActiveDoc
'added code: here I want to call the second macro and send it distance_of_second_plane, and have it use that value
distance_of_second_plane = 0.05
'example of calling the subroutine
boolstatus = Module1.InsertPlane(swApp, distance_of_second_plane)
End Sub
The modules can be exported and imported, so you can reuse them in other macros, to do that right click in the project tree on Module1 and export file. Similarly, you can right click on the Modules folder and Import modules.
Hope this helps.
While it is possible to run a macro from another macro using ISldWorks::RunMacro2, it is not possible to have this method return any values that you can use in your first macro. Even if this was possible, I would not recommend it.
Everything you need to accomplish can be accomplished in a single macro, you simply need to learn how to use the SolidWorks API to reach that end. Could you please explain what you are trying to accomplish with your macro? Then I could show you what code you need.
Please also note that the macro recorder is really not a good tool for creating macros of any significance. If you plan on using the SolidWorks API seriously, then you really need these three skills under your belt:
How to do basic programming with VBA (variables, arrays, conditionals, loops, etc)
How to navigate and read the SolidWorks API Help (the offline version)
How API objects are related to one another (SolidWorks API Object Model)
I have some videos at my web site (in my profile) that can help you get started. Again, if you want me to help here with your current problem, please explain what you're trying to automate.

Kill Excel on Error

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.

Previous Excel instance not getting through IIS

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.

My vb.net program for Excel won't run on different computer

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