Null reference exception when trying to open a workbook, vb.net - vb.net

I've got an openfiledialog reading a spreadsheet file name from a textbox, then performing some formatting and spitting out a text file. The code works fine one time through; my next task is to get it so that I can open successive spreadsheets (one at a time) without closing the program.
When I try to open a second excel file, I get a null reference exception (object ref not set to an instance of an object) on the line where I'm opening the workbook.
Public Class Form1
Dim xlApp As New Microsoft.Office.Interop.Excel.Application
Dim xlWorkbook, xlWorkbook2 As Microsoft.Office.Interop.Excel.Workbook
Dim xlWsheet, xlWsheet2 As Microsoft.Office.Interop.Excel.Worksheet
Dim strm As System.IO.Stream
Dim FormFile As String = "C:\nitemp.tmp\QuantData.xls"
Private Sub Open_Click(sender As Object, e As EventArgs) Handles Open.Click
'Open button code'
OpenFileDialog1.Title = "Select a File"
OpenFileDialog1.InitialDirectory = directory.Text 'uppermost text box, change to open a different default directory with OPEN button'
OpenFileDialog1.RestoreDirectory = True
OpenFileDialog1.ShowDialog()
End Sub
Private Sub OpenFileDialog1_FileOk(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles OpenFileDialog1.FileOk
Dim lrow As Integer = 0
Try
strm = OpenFileDialog1.OpenFile()
TextBox1.Text = OpenFileDialog1.FileName.ToString()
xlWorkbook = xlApp.Workbooks.Open(TextBox1.Text) 'opens excel file'
xlApp.Visible = False
strm.Close()
I see two possible null references here: 1) the .ToString on the previous line may be empty. WI run the code, the textbox isn't being populated by the correct file path. Only after the error box pops up and I hit 'continue' does the textbox show the correct path.
2) an issue with the platform, 64x vs 32x? This came up in a search, so I tried "New Excel.App" and "New Excel.Workbook", then changed to x86 platform, yet this got me the infamous COM exception 80040154, which makes me think this is not really the issue, but I'm still pretty new to coding..
Can someone find the null?

First things first, you don't need to call OpenFileDialog1.OpenFile(). In fact you don't need to get the filestream at all as you aren't manipulating the file directly (only Excel is).
Secondly, you need to retrieve and dispose of the xlApp.Workbooks collection independently, otherwise you are going to leak some COM wrappers. The null reference exception could be from either the Workbooks collection being null, or the open filename being null. Some error handling will solve your problem.
...
Dim xlWorkbooks as Excel.Workbooks
Dim xlWorkbookOpened as Excel.Workbook
Try
TextBox1.Text = OpenFileDialog1.FileName.ToString()
If (TextBox1.Text IsNot Nothing) Then
xlWorkbooks = xlApp.Workbooks
If (xlWorkbooks IsNot Nothing) Then
xlWorkbookOpened = xlWorkbooks.Open(TextBox1.Text) 'opens excel file'
If (xlWorkbookOpened IsNot Nothing) Then
' DO WHATEVER YOU NEED TO...
Marshal.ReleaseComObject(xlWorkbookOpened)
xlWorkbookOpened = Nothing
End If
Marshal.ReleaseComObject(xlWorkbooks)
xlWorkbooks = Nothing
End If
End If
Catch ex As Exception
' Log error to start with...
Trace.WriteLine(ex.Message)
End Try
Note that I've explicitly released every COM object after use and set the value to Nothing. This is necessary to ensure proper cleanup.

Related

How to properly quit Excel

I am trying to print a receipt from Excel but unable to do so. I have tried release in reverse but I seems can't find what is missing. Kindly help! Thank you!
This is what i have done so far :
Imports Excel = Microsoft.Office.Interop.Excel
Public Class Form1
#Region "dim"
Dim exeDir As New IO.FileInfo(Reflection.Assembly.GetExecutingAssembly.FullName)
Dim xlPath = IO.Path.Combine(exeDir.DirectoryName, "SampleReceipt.xls")
Dim app As Excel.Application = Nothing
Dim books As Excel.Workbooks = Nothing
Dim book As Excel.Workbook = Nothing
Dim sheets As Excel.Sheets = Nothing
Dim sheet As Excel.Worksheet = Nothing
Dim cell As Excel.Range = Nothing
#End Region
Private Sub NAR(ByVal o As Object)
Try
System.Runtime.InteropServices.Marshal.ReleaseComObject(o)
Catch ex As Exception
o = Nothing
Finally
End Try
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Try
app = New Excel.Application()
books = app.Workbooks
book = books.Open(xlPath)
sheets = book.Sheets
sheet = book.Sheets(1)
cell = sheet.Range("A1")
cell.Value = "Lorem Ipsum"
book.SaveAs("C:\Temp\ExcelBook.xls")
book.Close()
app.Quit()
Finally
NAR(cell)
NAR(sheet)
NAR(sheets)
NAR(book)
NAR(books)
NAR(app)
End Try
End Sub
End Class
This is a bit of a guess, but I don't think your NAR method is doing the job perfectly well.
You have this:
Private Sub NAR(ByVal o As Object)
Try
System.Runtime.InteropServices.Marshal.ReleaseComObject(o)
Catch ex As Exception
o = Nothing
Finally
End Try
End Sub
Now, if you have an Exception raised by ReleaseComObject then you're just swallowing it - I would think that you want to see what the exception is. You really should only handle exceptions that you can recover from. Handling the top-level Exception really is an anti-pattern. So if ReleaseComObject is raising an exception then find out what it is and deal with that specifically.
Next, if you do have an exception you are then trying to set the reference to Nothing, but you're only setting a copy of the reference to Nothing. You're passing in o using ByVal. The original reference is untouched. You want to pass it in ByRef. Try that.
Also, It might be required that you set the reference to Nothing after releasing the component - so move it out of the Catch.
And finally, the help docs on ReleaseComObject says this:
If you want to call this method to ensure that a COM component is released at a determined time, consider using the FinalReleaseComObject method instead. FinalReleaseComObject will release the underlying COM component regardless of how many times it has re-entered the CLR. The internal reference count of the RCW is incremented by one every time the COM component re-enters the CLR. Therefore, you could call the ReleaseComObject method in a loop until the value returned is zero. This achieves the same result as the FinalReleaseComObject method.
I'd try those three things.
Try this code:
Private Sub NAR(ByRef o As Object)
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(o)
o = Nothing
End Sub
And, only put in exception handling if you have a specific exception to handle, but keep the o = Nothing out of the handling code.

Update one form based on selections from another form

I apologise if the title is a bit vague, i've only been on here a day.
So my problem is I have a menu form in which I input the options from the comboboxes. And then I go to the next form which shows the relevant imported text file info.
However when I click the 'back' button to return to the menu and input different information in the comboboxes, it doesn't take me to the correct text file info, it just shows the info from the previous selection.
here is the student menu pic
here is the text file form
below is the code for the student menu next button:
If OptionBox.Text = "Introduction" Then
Introduction.Show()
Else
If OptionBox.Text = "Explanation" Then
Explanation.Show()
End If
End If
End Sub
below is the code for the text file form load page and the back button
Private Sub Introduction_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Student_Menu.Hide()
Dim font As New System.Drawing.Font("Calibri", 11)
If Student_Menu.TopicSelect.Text = "Computer Systems" Then
Dim strFile As String = "C:\Users\Sales\Documents\Visual Studio 2010\Projects\gcsecomputingtask\textfiles\Introduction\ComputerSystems.txt"
Dim sr As New IO.StreamReader(strFile)
IntroductionLabel.Text = sr.ReadToEnd()
sr.Close()
Else
If Student_Menu.TopicSelect.Text = "Hardware" Then
Dim strFile As String = "C:\Users\Sales\Documents\Visual Studio 2010\Projects\gcsecomputingtask\textfiles\Introduction\Hardware.txt"
Dim sr As New IO.StreamReader(strFile)
IntroductionLabel.Text = sr.ReadToEnd()
sr.Close()
Else
If Student_Menu.TopicSelect.Text = "Software" Then
Dim strFile As String = "C:\Users\Sales\Documents\Visual Studio 2010\Projects\gcsecomputingtask\textfiles\Introduction\Software.txt"
Dim sr As New IO.StreamReader(strFile)
IntroductionLabel.Text = sr.ReadToEnd()
Else
If Student_Menu.TopicSelect.Text = "Representation of Data" Then
Dim strFile As String = "C:\Users\Sales\Documents\Visual Studio 2010\Projects\gcsecomputingtask\textfiles\Introduction\RepresentationOfData.txt"
Dim sr As New IO.StreamReader(strFile)
IntroductionLabel.Text = sr.ReadToEnd()
Else
If Student_Menu.TopicSelect.Text = "Databases" Then
Dim strFile As String = "C:\Users\Sales\Documents\Visual Studio 2010\Projects\gcsecomputingtask\textfiles\Introduction\Databases.txt"
Dim sr As New IO.StreamReader(strFile)
IntroductionLabel.Text = sr.ReadToEnd()
Else
If Student_Menu.TopicSelect.Text = "Communications & Networks" Then
Dim strFile As String = "C:\Users\Sales\Documents\Visual Studio 2010\Projects\gcsecomputingtask\textfiles\Introduction\Hardware.txt"
Dim sr As New IO.StreamReader(strFile)
IntroductionLabel.Text = sr.ReadToEnd()
End If
End If
End If
End If
End If
End If
IntroductionLabel.Font = font
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBack.Click
Me.Hide()
Student_Menu.Show()
Student_Menu.TopicSelect.ResetText()
Student_Menu.OptionBox.ResetText()
End Sub
what do i need to do in order to update this information so that the program doesn't skip going through the form again.
There is a lot of repeated code there. Here is a way to reduce it (see DRY) and expose a method to change the topic. In a Module:
Public Enum Topics
ComputerSystems
Hardware
Software
Data
Database
Networks
End Enum
Then in the form that shows the text:
Friend Sub DisplayTopic(topic As Topics)
Dim text As String
Dim filname As String = ""
Select Case topic
Case Topics.ComputerSystems
filname = "ComputerSystems.txt"
Case Topics.Database
filname = "Databases.txt"
'... etc
End Select
' attach path
filname = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
"gcsecomputingtask", filname)
text = File.ReadAllText(filname)
IntroductionLabel.Text = text
End Sub
By the way, VB does has an ElseIf which can avoid the "arrow" anti pattern you can see in your code. At the very least, the excessive indentation is annoying.
If topic = Topics.ComputerSystems Then
'...
ElseIf topic = Topics.Software Then
'...
End If
Show that form normally using an instance of the form class:
Public Class MenuForm ' a form is just a class
' declare an object variable to use
Private info As Form2 ' whatever its name is (Explanation???)
....
Private Sub MenuForm_Load(...)
' create an instance to be used later:
info = New Form2
Then invoke the method to tell it which topic to display. Displaying topic info is a separate method from loading a form first because the form load event happens only once. A specialized method to do what we want makes more sense since they really have nothing to do with one another, and makes it easier to see how the code works:
info.DisplayTopic(Topics.ComputerSystems)
info.Show
This way, you dont have one form fiddling with the controls on another, but still have a clear way of communicating which topic to display.
Note that the location of the topics file(s) is a bit different. You'd want a "gcsecomputingtask" folder in MyDocuments for the files. The VS project folder is not a good place for it, the folder location could change depending on which machine you are running on (yours or computer lab etc). They could also be stored as a resource to skip that part too.

Export data from VB to Excel sheet

On VS 2012 I have created a VB.NET calculation application (outputs based on variable inputs), i need to save these input & output data to certain cells in excel sheet,
here is a sample of the code i use:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim xls As Microsoft.Office.Interop.Excel.Application
Dim xlsWorkBook As Microsoft.Office.Interop.Excel.Workbook
Dim xlsWorkSheet As Microsoft.Office.Interop.Excel.Worksheet
Dim misValue As Object = System.Reflection.Missing.Value
xls = New Microsoft.Office.Interop.Excel.Application
xlsWorkBook = xls.Workbooks.Open("D:\bookl.xlsx")
xlsWorkSheet = xlsWorkBook.Sheets("sheet1")
xlsWorkSheet.Cells(1, 1) = TextBox1.Text
xlsWorkBook.Close()
xls.Quit()
End Sub
my problem here is that in every time i click the save button it saves the data to a excel sheet file which i have to specify its path previously.
What i wish to do is if there is any way to load from VB it self then choose where to save it (because iam going to use it in a lot of machines, so i don't want to put the excel file in the same path each time i use the application in any other machine)
Open Excel file from My.Resources location
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim xlsWorkBook As Microsoft.Office.Interop.Excel.Workbook
Dim xlsWorkSheet As Microsoft.Office.Interop.Excel.Worksheet
Dim xls As New Microsoft.Office.Interop.Excel.Application
Dim resourcesFolder = IO.Path.GetFullPath(Application.StartupPath & "\..\..\Resources\")
Dim fileName = "book1.xlsx"
xlsWorkBook = xls.Workbooks.Open(resourcesFolder & fileName)
xlsWorkSheet = xlsWorkBook.Sheets("Sheet1")
xlsWorkSheet.Cells(1, 1) = TextBox1.Text
xlsWorkBook.Close()
xls.Quit()
MsgBox("file saved to " & resourcesFolder)
End Sub
The resource template xlsx file must be copied to the output directory, so edit its properties and choose (actually I'm not super sure you need this...)
Build Action = Content
Copy To Output Directory = Copy Always
P.S. this is just a sample to use with your current code, but I strongly suggest using EPPlus Library if you want to create/save/modify excel files.

Checking if text file is empty

I have the following code:
Private Sub btnCreateAccount_Click(sender As Object, e As EventArgs) Handles btnCreateAccount.Click
Dim fi As New System.IO.FileInfo(strUsersPath)
Using r As StreamReader = New StreamReader(strUsersPath)
Dim line As String
line = r.ReadLine ' nothing happens after this point
Do While (Not line Is Nothing)
If String.IsNullOrWhiteSpace(line) Then
MsgBox("File is empty, creating master account")
Exit Do
Else
MsgBox("Creating normal account")
End If
line = r.ReadLine
Loop
End Using
End Sub
I am having some problems. Basicaly I have a streamreader opening up a .txt file where the directory is stored in 'strUsersPath'. I am trying to get the code so that if the file is empty, it does one thing, and if the file is not empty (there is a user) then it does another.
If I have a user in my txt file, the code gives the msgbox("creating normal account"), as expected, however when I do not have a user, it does not give me the other msgbox, and I can't seem to work out why. I suspect it is because IsNullOrWhiteSpace is not the right thing to use for this. Any help would be greatly appreciated
EDIT
This is the code I have also tried, same result, clicking the button does nothing if there is already a user.
Private Sub btnCreateAccount_Click(sender As Object, e As EventArgs) Handles btnCreateAccount.Click
Dim fi As New System.IO.FileInfo(strUsersPath)
Using r As StreamReader = New StreamReader(Index.strUsersPath)
Dim line As String
line = r.ReadLine ' nothing happens after this point
Do While (Not line Is Nothing)
fi.Refresh()
If Not fi.Length.ToString() = 0 Then
MsgBox("File is empty, creating master account") ' does not work
Exit Do
Else
MsgBox("Creating normal account") ' works as expected
End If
line = r.ReadLine
Loop
End Using
End Sub
You do not need a StreamReader for this. All you need is File.ReadAllText
If File.ReadAllText(strUsersPath).Length = 0 Then
MsgBox("File is empty, creating master account")
Else
MsgBox("Creating normal account")
End If
I recommend using this method
If New FileInfo(strUsersPath).Length.Equals(0) Then
'File is empty.
Else
'File is not empty.
End If

Excel VSTO Workbooks.Open only working when another action is taken first

I am working on a VSTO add-in. I have a customized ribbon, and on that ribbon a button called TemplateCallButton. I also have several other functions and buttons, one of which just opens a folder with templates (included as example). The TemplateCallButton only works and adds in a template file if one of the other actions has been completed (seemingly doesn't matter which one). After any other action has run then it works as expected.
What's more frustrating is that this behavior only seems to happen on machines I deploy on, and not the one I'm developing on. Here is the TemplateCallButton code:
Public Class InsightLabProcessor
Dim MainTemplatePath As String = "C:\Insight\Insight.xltm"
....
Private Sub TemplateCallButton_Click(sender As Object, e As RibbonControlEventArgs) Handles TemplateCallButton.Click
Dim objApp As Excel.Application
objApp = Marshal.GetActiveObject("Excel.Application")
objApp.Visible = True
Dim objWorkbook As Excel.Workbook = objApp.Workbooks.Open(MainTemplatePath)
objWorkbook.Worksheets(4).Activate()
End Sub
and here is the code for the button that just opens a folder:
Private Sub PhaseCodeFolderOpenButton_Click(sender As Object, e As RibbonControlEventArgs) Handles PhaseCodeFolderOpenButton.Click
Process.Start("explorer.exe", "C:\Insight\Phase Codes")
End Sub
or one that opens the control form:
Private Sub ControlPannel_Click(sender As Object, e As RibbonControlEventArgs) Handles ControlPannel.Click
Dim controlpanel As New ControlPanel
controlpanel.Show()
controlpanel = Nothing
End Sub
I feel like I must be missing something simple.
Thanks.
So the problem is in fact the one addressed here: http://support.microsoft.com/kb/238610, which seems pretty vicious to deal with as an add-in. The best solution I've found (again not very elegant) is to just open the command line, write out that we're waiting for the first instance to load, the close it before anyone get's too curious. I tried this on 4 machines and empirically found the longest wait time I needed was 250 ms, so I doubled it to 500 in this:
...
Shell("C:\Windows\System32\cmd.exe", AppWinStyle.MaximizedFocus)
System.Threading.Thread.Sleep(10) 'give cmd just a bit to take the line
SendKeys.Send("Waiting for Excel to register in Running Object Table")
System.Threading.Thread.Sleep(490)
Dim Term() As Process = Process.GetProcessesByName("cmd")
For Each P As Process In Term
P.Kill() 'user probably doesn't have any other instances of cmd open, if they do they are colaterial damage, may handle that if it becomes an issue
Next
Dim objApp As Excel.Application
objApp = Marshal.GetActiveObject("Excel.Application")
objApp.Visible = True
Dim objWorkbook As Excel.Workbook = objApp.Workbooks.Open(MainTemplatePath)
objWorkbook.Worksheets(4).Activate()
Again I would come back and except anything that was more elegant or acceptable to an end user. I would really love to know if there was a way to force Excel to register to the ROT. Perhaps I should turn that into another question.