Excel Automation - how to just say No - vb.net

I'm working on a vb.net console app that opens a number of spreadsheets one by one, reads in a couple cells and closes the file.
Some of the spreadsheets have formulas that Excel recalculates when I open the file, and I'm getting a dialog box asking me if I want to save changes to the spreadsheet when I close it.
Message: "Do you want to save the changes to myfile.xls? Microsoft Office Excel recalculates formulas when opening files last saved by an earlier version of Excel"
How do I automatically decline that dialog box?
Dim newCBD As New CBD
Dim xlApp As Excel.Application
Dim xlWorkBook As Excel.Workbook
Dim xlWorkSheet As Excel.Worksheet
xlApp = New Excel.ApplicationClass
xlWorkBook = xlApp.Workbooks.Open(myFile)
xlWorkSheet = xlWorkBook.Worksheets(1)
...do a bunch of junk here
xlWorkBook.Close()
xlApp.Quit()
Thanks!

You need to supply a False parameter on the Close method
xlWorkBook.Close(False)
From Excel VBA Help:
Close method as it applies to the
Workbook object.
Closes the object.
expression.Close(SaveChanges,
Filename, RouteWorkbook)
expression Required. An expression
that returns one of the above objects.
SaveChanges Optional Variant. If
there are no changes to the workbook,
this argument is ignored. If there are
changes to the workbook and the
workbook appears in other open
windows, this argument is ignored. If
there are changes to the workbook but
the workbook doesn't appear in any
other open windows, this argument
specifies whether changes should be
saved, as shown in the following
table.
Value Action True Saves the changes
to the workbook. If there is not yet a
file name associated with the
workbook, then FileName is used. If
FileName is omitted, the user is asked
to supply a file name. False Does not
save the changes to this file.
Omitted Displays a dialog box asking
the user whether or not to save
changes.
FileName Optional Variant. Save
changes under this file name.
RouteWorkbook Optional Variant. If
the workbook doesn't need to be routed
to the next recipient (if it has no
routing slip or has already been
routed), this argument is ignored.
Otherwise, Microsoft Excel routes the
workbook as shown in the following
table.
Value Meaning True Sends the workbook
to the next recipient. False Doesn't
send the workbook. Omitted Displays a
dialog box asking the user whether the
workbook should be sent.

Per this documentation: http://msdn.microsoft.com/en-us/library/bb223560.aspx
If you set the Saved property for a workbook to True without saving the workbook to the disk, Microsoft Excel will quit without asking you to save the workbook.

It is good to use Close() to avoid this error message but what if you are using the version like v11.0 which have close() that takes no parameter so the problem still does not solve for this case. To deal with this issue you may use Marshal.RealeaseCOMObject BUT remember there are some issues with that.
In response to your "how to just say No", you can use:
xlApp.DisplayAlerts= false; // use just above the Close() method calls
xlApp.Workbooks.Close(); // here no arguments can be used.
Hope it helps!

Related

VBA - excel closes the previous workbook on opening the new one

I have a strange problem, I suscpect it's connected to the version of the Excel, but I'm not sure at all. I can't figure it out alone so I need your help. I have a macro, which operates on a fresh workbook - it's not saved anywhere, as the worker will save it manually afterwards. The macro is a .xlam format add-in, adding a couple of buttons to the ribbon and these buttons start the code.
Inside the code I have simple lines for opening a new workbook, chosen earlier by an user:
Application.DisplayAlerts = False
Set wbMPA = Workbooks.Open(MPA_file)
Application.DisplayAlerts = True
Earlier, the code sets active workbook as an object/workbook the macro will mainly work on (tried both versions):
Set dwb = Application.ActiveWorkbook
and later in the code
dwb.activate
OR:
dwb = ActiveWorkbook.Name
and then
workbooks(dwb).Activate
The lines are in separate subs, but the variable is globally declared.
The code works fine until the opening of wbMPA (watching it in the locals all the time). When I try to open the new file with the code above, the earlier workbook (dwb) just closes itself from unknown reasons.
The error I get from the 1st method is this:
error screenshot
The second one gives a simple "Subscipt out of range".
The errors, however, are not a problem. The problem is the cause of them, which is closing of the workbook from unknown reasons.
It happens only when I open the completely new workbook (using the excel icon on the Start bar) - when I do it from File -> New -> Blank Workbook using already opened workbook, the error does not occur.
Another strange thing - me and my colleague from work use 2013 version of Excel. I never have this error, she has it every time.
This is a general scheme of the code, other things are meaningless in this case because there are no other manipulations of workbooks/worksheets.
Dim dwb As Object
Dim wbMPA As Object
Sub_1()
Set dwb = ActiveWorkbook
Set wbMPA = Workbooks.Open(MPA_file)
Call Sub_2
End Sub
Sub_2()
dwb.Activate
End Sub
I get an error on the activation of dwb in Sub_2, because it closes itself for God knows what the reason on the opening of wbMPA in the Sub_1.
If you have only opened a blank workbook (clicking Excel from Toolbar, for example) and then you open any named workbook before making any changes to the blank workbook, the blank workbook will disappear. I believe that is normal/expected behavior.
I can't speculate why this happens on one computer but not another, but this is always how I have observed new/blank documents (Excel, PowerPoint, Word) to behave, and assume this to be the normal behavior. You may have some different option/configuration on your Excel environment which is changing this default behavior, or maybe you are slightly altering the blank file before running the macro, and your co-worker isn't, etc.
A word of caution to avoid relying on ActiveWorkbook -- and especially in this case if the expectation is to always Set dwb to a new/blank workbook, the best way to do that is to explicitly create a new/blank workbook, rather than relying on the user to manually open a new/blank target workbook.
Set dwb = Workbooks.Add
If, on the other hand dwb must be assigned to some other known/existing workbook, then you should be either providing the file path to an Open statement, or the workbook name to the Workbooks collection.
On a related note, it's almost never necessary to Activate a workbook, see here:
How to avoid using Select in Excel VBA macros
And further note: your variables aren't globally scoped, they're scoped only to the module using Dim statement. A public declaration uses the Public keyword, not the Dim keyword. Both module-scoped and global-scoped should be used with caution (Public moreso than module-scoped) and in most cases it's preferable to pass objects/variables by reference to dependent subs and functions:
How to make Excel VBA variables available to multiple macros?

Catching FileFormat property of .XLAM without confusion with .XLSM

Context
I have developed a .xlam add-in that contains user-data inside. In other words, the user can decide to show the add-in file through a ThisWorkbook.IsAddIn = False to edit the content, which is functional to the add-in itself.
However, the user should not be able to perform some operations when he's/she's working on the add-in's spreadsheets rather than on the normal workbook where the Add-In is running.
Need to check for file extension
From here, it comes my need of checking for the file extension and validate it when some specific "forbidden" procedures might get called. I have made the following tests:
If ThisWorkbook.IsAddIn = True, then ThisWorkbook.FileFormat = 55;
If ThisWorkbook.IsAddIn = False, then ThisWorkbook.FileFormat = 52;
The source of confusion
This is not what I was expecting. By simply executing a FullName request when the Add-In is set visible:
ThisWorkbook.IsAddIn = False
MsgBox ThisWorkbook.FullName
I can read that my file is still named C:\myFile.xlam, even if in that moment is visible to the user. So, I would expect ThisWorkbook.FileFormat to raise a 55 even if visible at run-time. But it doesn't do that, apparently.
The question
I need to make sure to distinguish between modifications on the Add-In (.xlam) and modifications on a possible .xlsm file that the user created, from which is using my Add-In.
Why is the FileFormat of my add-in being equal to the one of an xlsm, if the file is clearly xlam to which is associated a 55 instead of a 52? Where am I being wrong?
EDIT - Example of the action to forbid
On the ribbon there's a button created and added from the add-in, which is connected to a macro that cannot be run into the Add-In. So the check I had in mind was something like this:
If ActiveWorkbook.FileFormat = 55 Then
Exit Sub
End If
However, as said above, this check will not be performed because the Add-In has FileFormat = 52 in the moment in which is set to .IsAddIn = False; hence, even if the ActiveWorkbook is the add-in where I do not want to run the macro, the check will fail and the macro will run anyway.
The .IsAddIn workbook property simply indicates whether the file is being run as an Add-in. It does not change the file format. From the documentation:
When you set this property to True, the workbook has the following characteristics:
You won’t be prompted to save the workbook if changes are made while the workbook is open.
The workbook window won’t be visible.
Any macros in the workbook won’t be visible in the Macro dialog box (displayed by pointing to Macro on the Tools menu and clicking Macros).
Macros in the workbook can still be run from the Macro dialog box even though they’re not visible. In addition, macro names don’t need to be qualified with the workbook name.
Holding down the SHIFT key when you open the workbook has no effect.
I sense that this is the real problem you're trying to tackle:
However, the user should not be able to perform some operations when he's/she's working on the add-in's spreadsheets rather than on the normal workbook where the Add-In is running.
Perhaps it will be best if you can specify what actions you're trying to restrict? There may be a better way to solve this.
For the moment I have found four possible solutions, that I'm going to post here just in case someone would have my same issue:
Comparing the full names - credit to Tim Williams
The "special code" cannot run if the full names are different:
If ActiveWorkbook.FullName = ThisWorkbook.FullName Then
Exit Sub
End If
'"special code"
Comparing the isAddIn property - credit to David Zemens
The "special code" cannot run if this workbook is not currently an add-in:
If ThisWorkbook.IsAddIn = False Then
Exit Sub
End If
'"special code"
Comparing the two objects
The "special code" cannot run if the active workbook is the add-in workbook:
If ActiveWorkbook Is ThisWorkbook Then
Exit Sub
End If
'"special code"
Checking for "xlam" extension
The "special" code will not be run if the extension of the file is xlam:
If Right(ActiveWorkbook.FullName,4) = "xlam" Then
Exit Sub
End If
The four solutions above work fine for the purpose, but the question is still opened : why the FileFormat property changes over the same file depending on ThisWorkbook.IsAddIn being False rather than True?

VBA - Open XLS App On-Top of MSAccess

I am creating and formatting an XLS Workbook from an Access Database using VBA, initiated via a user button press on a form.
I'd like to have the XLS App ALWAYS open on top of the AccessDB to permit the user to see the worksheet directly. Currently it is intermittent, XLS usually opens UNDER Access on first iteration, but then on-top in subsequent iterations in the same session. User review of the file is necessary prior to final save and export to our client portal. There will typically only be a single iteration per user session.
I am using the following approach (pseudo):
Dim appXLS As Object
Dim wbkNew As Object
Dim wks1, wks2, wks3 As Object
Set appXLS = CreateObject("Excel.Application")
Set wbkNew = appXLS.Workbooks.Add
appXLS.Visible = True
wbkNew.Activate
...populate & format 3 worksheets from Access
wks1.Activate ' go back to sheet 1
wbkNew.SaveAs AccessMode:=xlExclusive, FileName:=myFile
I am saving the file in this routine, so any "overwrite file" warnings also open under MSAccess.
I thought the .Activate lines would ensure the desired function. Any thoughts or ideas on this?
Thanks!

Vba closing excel applications/workbooks freezes

I have a problem with the following code:
Dim excelapp as object
set excelapp = CreateObject("excel.application")
dim ws as object
dim wb as Workbook
wb= excelapp.Workbooks.Open(path)
ws= wb.Sheets(1)
'in the code i send the worksheet object around by reference in order to read the
'worksheet and manipulate data, i dont create other instances of excel apps or
'workbooks
then i try :
wb.Close
and i have also tried :
excelapp.Quit
Neither have worked, they both freeze and say they are waiting on OLE actions, and i have multiple excel processes opening if i do not call these, when i try to open the excel files i had opened via code, i can only open them as read-only because theyre checked out to me.
I also tried executing a shell script that closes all applications "Excel.Exe" but it closes...the actual excel file where the vba is being executed, so thats not a good solution.
Thank you in advance.
It might be that the Excel app has detected that the workbook has changed and is putting up a dialog box (which is invisible because the app is not visible). Try:
wb.Close False
Which tells Excel to ignore any changes to the workbook.

Cancel External Query in Excel VBA

I have created an Excel Spreadsheet which helps with data analysis from an Oracle database.
The user enters then clicks the "Refresh Query" button which generates a query for Oracle to execute. The query takes a minute or so to complete. Although the VBA code does not hang on ".Refresh", all Excel windows remain frozen until the query completes.
Sub refreshQuery_click()
Dim queryStr as String
' Validate parameters and generate query
' ** Code not included **
'
' Refresh Query
With ActiveWorkbook.Connections("Connection").OLEDBConnection
.CommandText = queryStr
.Refresh
End With
End Sub
Is there a way for the user to manually cancel the query (calling .CancelRefresh) while the Excel user-interface is frozen?
EDIT I don't know if the following is worth noting or regular behavior. While the query is executing, all open Excel windows (including the VBA Editor) become "Not Responding" in Task Manager. Neither pressing Esc nor Ctrl+Break will cancel the script. Also, calling DoEvents (either before or after .Refresh) does not change this behavior.
Here's a method that I know will work. However, there are some complications.
Here's how it's done:
Put the spreadsheet with the data in a separate workbook. This worksheet should execute the refresh query when it's opened and then close once the data is updated.
Create a batch file to call the "Data" Excel file.
Within a different workbook, create a procedure (macro) for the user to call. This procedure will call the batch file, which subsequently calls the Excel file. Since you are calling a batch file and not Excel directly, the Excel procedure will continue because the command shell is released so quickly and opens the other Excel file in a different thread. This allows you to continue working within the main Excel file.
Here are some complications:
I included a method to alert the user that the data has been udpated. There are timing issues where it's possible to try to check if the data has been update when the workbook is not accessible, which forces the user to try to update values. I included a method called my time which pauses the execution of the code so it only checks every so many seconds.
The updated worksheet will pop up in a new window, so the user will need to click on their original worksheet and keep working. You could learn to hide this if you're comfortable with Windows scripting (I haven't learned that yet).
Here are some files and code. Be sure to read the comments in the code for why some things are there.
FILE: C:\DataUpdate.xls
We'll make a workbook called "DataUpdate.xls" and put it in our C:\ folder. In cell A1 of Sheet1, we'll add our QueryTable which grabs external data.
Option Explicit
Sub UpdateTable()
Dim ws As Worksheet
Dim qt As QueryTable
Set ws = Worksheets("Sheet1")
Set qt = ws.Range("A1").QueryTable
qt.Refresh BackgroundQuery:=False
End Sub
Sub OnWorkbookOpen()
Dim wb As Workbook
Set wb = ActiveWorkbook
'I put this If statement in so I can change the file's
'name and then edit the file without code
'running. You may find a better way to do this.
If ActiveWorkbook.Name = "DataUpdate.xls" Then
UpdateTable
'I update a cell in a different sheet once the update is completed.
'I'll check this cell from the "user workbook" to see when the data's been updated.
Sheets("Sheet2").Range("A1").Value = "Update Table Completed " & Now()
wb.Save
Application.Quit
End If
End Sub
In the ThisWorkbook object in Excel, there's a procedure called Workbook_Open(). It should look like the following so it executes the update code when it is opened.
Private Sub Workbook_Open()
OnWorkbookOpen
End Sub
NOTE: I found a bug when this file closed if 1) you accessed the file from the command line or shell and 2) you have the Office Live Add-in installed. If you have the Office Live Add-in installed, it will throw an exception on exit.
FILE: C:\RunExcel.bat
Next, we're going to create a batch file that will open the Excel file we just made. The reason that call the Excel file from within the batch file and not directly from the other Excel file using Shell is because Shell will not continue until the other application closes (at least when using Excel.exe "c:\File.xls"). The batch file, however, runs its code and then immediately closes, thus allowing the original code that called it to continue. This is what will let your uses continue working in Excel.
All this file needs is:
cd "C:\Program Files\Microsoft Office\Office10\"
Excel.exe "C:\DataUpdate.xls"
If you're handy with Windows Scripting, you do fancy things like open the window in a hidden mode or pass a parameter of the file name or Excel location. I kept it simple with a batch file.
FILE: C:\UserWorkbook.xls
This is the file that the user will open to "do their work in." They'll call the code to update the other workbook from within this workbook and they'll still be able to work in this workbook while this one is updating.
You need a cell in this workbook where you'll check the "Update Table Completed" cell from the DataUpdate workbook. I chose cell G1 in Sheet1 for my example.
Add the following code to a VBA module in this workbook:
Option Explicit
Sub UpdateOtherWorkbook()
Dim strFilePath As String
Dim intOpenMode As Integer
Dim strCallPath As String
Dim strCellValue As String
Dim strCellFormula As String
Dim ws As Worksheet
Dim rng As Range
Set ws = Worksheets("Sheet1")
Set rng = ws.Range("G1")
strCellFormula = "='C:\[DataUpdate.xls]Sheet2'!A1"
'This makes sure the formula has the most recent "Updated" value
'from the data file.
rng.Formula = strCellFormula
strFilePath = "C:\RunExcel.bat"
intOpenMode = vbHide
'This will call the batch file that calls the Excel file.
'Since the batch file executes it's code and then closes,
'the Excel file will be able to keep running.
Shell strFilePath, intOpenMode
'This method, defined below, will alert the user with a
'message box once the update is complete. We know that
'the update is complete because the "Updated" value will
'have changed in the Data workbook.
AlertWhenChanged
End Sub
'
Sub AlertWhenChanged()
Dim strCellValue As String
Dim strUpdatedCellValue As String
Dim strCellFormula As String
Dim ws As Worksheet
Dim rng As Range
Set ws = Worksheets("Sheet1")
Set rng = ws.Range("G1")
strCellFormula = "='C:\[DataUpdate.xls]Sheet2'!A1"
strCellValue = rng.Value
strUpdatedCellValue = strCellValue
'This will check every 4 seconds to see if the Update value of the
'Data workbook has been changed. MyWait is included to make sure
'we don't try to access the Data file while it is inaccessible.
'During this entire process, the user is still able to work.
Do While strCellValue = strUpdatedCellValue
MyWait 2
rng.Formula = strCellFormula
MyWait 2
strUpdatedCellValue = rng.Value
DoEvents
Loop
MsgBox "Data Has Been Updated!"
End Sub
'
Sub MyWait(lngSeconds As Long)
Dim dtmNewTime As Date
dtmNewTime = DateAdd("s", lngSeconds, Now)
Do While Now < dtmNewTime
DoEvents
Loop
End Sub
As you can see, I constantly updated the formula in the "Listening Cell" to see when the other cell was updated. Once the data workbook has been updated, I'm not sure how you'd force an update in code without rewriting all the cells. Closing the workbook and reopening it should refresh the values, but I'm not sure of the best way to do it in code.
This whole process works because you're using a batch file to call Excel into a different thread from the original file. This allows you to work in the original file and still be alerted when the other file has been updated.
Good luck!
EDIT: Rather than include a more complete answer in this same answer, I've created a separate answer dedicated entirely to that solution. Check it out below (or above if it gets voted up)
Your users can break the VBA function by pressing Ctrl+Break on the keyboard. However, I've found that this can cause your functions to randomly break until each time any function is run. It goes away when the computer is restarted.
If you open this file in a new instance of Excel (meaning, go to Start > Programs and open Excel from there), I think that the only workbook that will be frozen will be the one executing the code. Other intances of Excel shouldn't be affected.
Lastly, you might research the DoEvents functions, which yields execution back to the Operating System so that it can process other events. I'm not sure if it would work in your case, but you could look into it. That way you can do other things while the process is being completed (It's kind of dangerous because the user can then change the state of your application while the process is working).
I believe I know a way that actually will work, but it's complicated and I don't have the code in front of me. It involves creating a separate instance of the Excel application in code and attaching a handler to the execution of that instance. You include the DoEvents part of the code in a loop that releases once the application closes. The other instantiated Excel application has the sole purpose of opening a file to execute a script and then close itself. I've done something like this before so I know that it works. I'll see if I can find the code tomorrow and add it.
Well, you could consider the old-fashion way -- split the query into smaller batches and use Do Events in between batches.
You could try XLLoop. This lets you write excel functions (UDfs) on an external server. It includes server implementations in many languages (eg. Java, Ruby, Python, PHP).
You could then connect to your oracle database (and potentially add a caching layer) and serve up the data to your spreadsheet that way.
The XLL also has a feature to popup a "busy" GUI that lets the user cancel the function call (which disconnects from the server).
BTW, I work on the project so let me know if you have any questions.