So I'm working on a VBA function in Access 2010 that creates a Word.Application object and returns it. Later on I want to be able to close this object, however not if its a Word.Application object that was not started by my program.
Public myGlobalWordApp as Object
Public newWordAppInstCreated as Boolean
Function GetWordAppInstance(Optional isVisible As Boolean = False) As Object
newWordAppInstCreated = False
On Error Resume Next
Set myGlobalWordApp = GetObject(, "Word.Application")
If myGlobalWordApp = Nothing Then
myGlobalWordApp = CreateObject("Word.Application")
myGlobalWordApp.Visible = isVisible
newWordAppInstCreated = True
End If
Set GetWordAppInstance = myGlobalWordApp
End Function
I want to make a CloseWordAppInstance() Sub that closes the myGlobalWordApp application object BUT ONLY if my VBA code was the one to open it. Sometimes I'll have another Word document up that I'm looking at and I don't want that window to be closed.
I've looked at the Word 2010 Application documentation and I saw that there is a .Parent method that I can call. I imagine that I could use this to see if I can determine if my Access Document/Module/Application created the Word.Application object but I dont know how to reference the "current object" or know how to do the comparison. Any help on that step would be appreciated.
My only "impropper" way of doing this would be checking the Boolean flags that I attempted to make, but that wont work if I make a second object.
Alternatively if anyone knows a better way to do this process that would be great!
You need to make sure that after you have created a word instance, you keep its reference. You can then use the same reference to close the word instance.
Private myGlobalWordApp As Object
Public Function GetWordAppInstance(Optional isVisible As Boolean = False) As Object
On Error GoTo ErrH
If myGlobalWordApp Is Nothing Then
'' create a new word instance if one doesn't exist
Set myGlobalWordApp = CreateObject("Word.Application")
myGlobalWordApp.Visible = isVisible
Set GetWordAppInstance = myGlobalWordApp
Else
'' otherwise return the instance we have
Set GetWordAppInstance = myGlobalWordApp
End If
Exit Function
ErrH:
MsgBox "Error creating word instance!", vbExclamation
End Function
Public Sub CloseWordInstance()
If Not myGlobalWordApp Is Nothing Then
'' close our word instance
myGlobalWordApp.Quit False
Set myGlobalWordApp = Nothing
End If
End Sub
You should never need to use the myGlobalWordApp variable directly. You can instead call the GetWordAppInstance to get the word instance and the CloseWordInstance to safely close it. This would ensure that you don't ever overwrite the myGlobalWordApp variable and lose reference to the word instance.
Public Sub Test()
Dim myWordInst As Object, wdDoc As Object
Set myWordInst = GetWordAppInstance(True)
Set wdDoc = myWordInst.Documents.Add
' do something with this doc here
CloseWordInstance
End Sub
Related
The Scenario: I tried to use this code to create an alternative OpenArgs property according to the suggestion of Rubberduck, but I cannot get this to work. I guess I'm missing something here, but have no idea what.
The Code:
Public varAltOpenArgs As Variant
Dim FormX as form
Public Property Let AltOpenArgs(value As Variant)
varAltOpenArgs = value
End Property
Public Property Get AltOpenArgs() As Variant
AltOpenArgs = varAltOpenArgs
End Property
A new instance of the form is opened from this form (form1) using:
Set frmX = New
frmX.AltOpenArgs = "abcde"
frmX.SetFocus
The problem: The property AltOpenArgs contains an empty string ("") when called in Form_Open.
Can anybody point me in the right direction? I'm using Access 2010 (32).
The problem is, all the events are firing as soon you create the new instance (Set frmX = New Form_MyExampleForm) before your next line of code runs where you set the value of AltOpenArgs.
This will never work because Access forms are made to be Instantiated by the Application and you don't really have control over the events. If you absolutely can not use the built in OpenArgs, then you could do something like this where you are running a routine from the Property Let:
Public Property Let AltOpenArgs(value As Variant)
varAltOpenArgs = value
FancyRoutine
End Property
Private Sub FancyRoutine()
'Do whatever fancy stuff you want to do
MsgBox AltOpenArgs
End Sub
I do something along the lines of what you are trying (though not using the Form_Open event) for subforms. It might look like this:
Dim frm As Form_MyExampleForm
Me.SubformControl.SourceObject = "MyExampleForm"
Set frm = MainSubform.Form
frm.AltOpenArgs = "whatever you want"
Private OutlookApp, Nms As Object
Sub TestSub()
Dim Fold As Object
Set OutlookApp = GetObject(, "Outlook.Application")
Set Nms = OutlookApp.GetNamespace("MAPI")
Set Fold = outlookFolderpath("Test Folder")
For Each Email In Fold.Items ' This loop doesnt work
Debug.Print Email.Subject
Next
End Sub
Private Function outlookFolderpath(Inbox As String) As Object
Dim fold_name As String
Set OutlookFolder_Path = Nms.Folders(Inbox).Folders("Inbox")
For Each Email In OutlookFolder_Path.Items ' This Loop works
Debug.Print Email.Subject
Next
End Function
Hello,
I was hoping someone could help me with the above code. I'm trying to Set and inbox folder path from a function and using it within the sub.
It works fine from within the function but not when setting it in the sub?
Can anyone see where I am going wrong? I get a runtime error '91' - Object variable or With block variable not set
so I would gather that the function is not returning the object but I'm not sure why?
thanks
You need to return the object from the function, so
Set OutlookFolder_Path = Nms.Folders(Inbox).Folders("Inbox")
should be
Set outlookFolderpath = Nms.Folders(Inbox).Folders("Inbox")
If you declare Option Explicit at the top of your code you will be less likely to run into problems like this as all your variable should be declared.
I have an Access 2007 app that I'm updating to be able to run on both 2007 and 2010. In 2007 I use the form ribbon property, but with 2010 I've needed to make a default ribbon that turns off the backstage. I've done that but the app needs too set it as default when it detects that it is running on 2010 instead of 2007. The Load custom UI does not work. It loads it but it does not set a ribbon as default. I know I can set the default start up form and other properties with the database.properties function. But I need to know the property name for the application default ribbon. Anyone know the property names?
I think the name of the Database Property your looking for is: CustomRibbonId
Here's some code to output a list of Database Properties to the Debug window.
Private Sub EnumerateDatabaseProperties()
On Error Resume Next
Dim p1 As DAO.Property, s1 As String
For Each p1 In CurrentDb.Properties
s1 = p1.Name
s1 = s1 & "=" & p1.value
Debug.Print s1
Next p1
End Sub
Do realize that a database property might not show up in the output if it doesn't exist, rather than just showing up in the output with no value.
First we need a robust method for setting DB properties.
Public Sub SetCurrentDBProperty(ByVal propertyName As String, ByVal newValue As Variant, Optional ByVal prpType As Long = dbText)
Dim thisDBs As Database
Set thisDBs = CurrentDb
Dim wasFound As Boolean
' Look for property in collection
Dim thisProperty As Object ' DAO.Property
For Each thisProperty In thisDBs.Properties
If thisProperty.Name = propertyName Then
' Check for matching type
If thisProperty.Type <> prpType Then
' Remove so we can add it back in with the correct type.
thisDBs.Properties.Delete propertyName
Exit For
End If
wasFound = True
' Skip when no change is required
If thisProperty.Value = newValue Then
Exit For
Else
' Update value
thisProperty.Value = newValue
End If
End If
Next thisProperty
If Not wasFound Then
' Add new property
Set thisProperty = thisDBs.CreateProperty(propertyName, prpType, newValue)
thisDBs.Properties.Append thisProperty
End If
End Sub
Then given an example ribbon name of Runtime you could call the property setter like this:
Public Sub SetRuntimeRibbon()
SetCurrentDBProperty "CustomRibbonID", "Runtime"
End Sub
Code to create new form instance of a closed form using form name
I want to replace the long Select Case list with a variable.
Full code of module
In Access 2010 I have a VBA function that opens a new instance of a form when given a string containing the form's name. By adding a form variable "frm" to a collection:
mcolFormInstances.Add Item:=frm, Key:=CStr(frm.Hwnd)
The only way I can figure out to open "frm" is with a Select Case statement that I've manually entered.
Select Case strFormName
Case "frmCustomer"
Set frm = New Form_frmCustomer
Case "frmProduct"
Set frm = New Form_frmProduct
... etc ... !
End Select
I want it to do it automatically, somewhat like this (although this doesn't work):
Set frm = New Eval("Form_" & strFormName)
Or through some code:
For Each obj In CurrentProject.AllForms 'or AllModules, neither work
If obj.Name = strFormName Then
Set FormObject = obj.AccessClassObject 'or something
End If
Next
Set frm = New FormObject
I just want to avoid listing out every single form in my project and having to keep the list updated as new forms are added.
I've also done some testing of my own and some reading online about this. As near as I can tell, it isn't possible to create a new form object and set it to an instance of an existing form using a string that represents the name of that form without using DoCmd.OpenForm.
In other words, unless someone else can prove me wrong, what you are trying to do cannot be done.
I think you are looking for something like this MS-Access 2010 function. (The GetForm sub is just for testing):
Function SelectForm(ByVal FormName As String, ByRef FormExists As Boolean) As Form
For Each f In Application.Forms
If f.Name = FormName Then
Set SelectForm = f
FormExists = True
Exit Function
End If
Next
FormExists = False
End Function
Sub GetForm(ByVal FormName As String)
Dim f As New Form
Dim FormExists As Boolean
Set f = SelectForm(FormName, FormExists)
If FormExists Then
MsgBox ("Form Found: " & f.Caption)
Else
MsgBox ("Form '" & FormName & "' not found.")
End If
End Sub
Here's an ugly hack I found:
DoCmd.SelectObject <acObjectType>, <YourObjectsName>, True
DoCmd.RunCommand acCmdNewObjectForm
The RunCommand step doesn't give you programmatic control of the object, you'll have to Dim a Form variable and Set using Forms.Item(). I usually close the form after DoCmd.RunCommand, then DoCmd.Rename with something useful (my users don't like Form1, Form2, etc.).
Hope that helps.
Are there ways in Access VBA (2003) to cast a COM reference to an integer, and to call AddRef/Release? (which give the error "Function or interface marked as restricted, or the function uses an Automation type not supported in Visual Basic")
I'm using a third-party COM object which doesn't handle being instantiated twice in a single process (this is a known bug). I therefore thought of storing the reference as the caption of a control on a hidden form to protect it from Program Reset clearing all VB variables.
Edit: I think the cast to int can be done with the undocumented ObjPtr, and back again with the CopyMemory API, and AddRef/Release can be called implicitly. But is there a better way? Are add-ins protected from Program Reset?
Is the problem with surviving the code reset or is it that once the code is reset it can't be re-initialized?
For the first problem, wrap your top-level object in a function and use a STATIC variable internally to cache the reference. If the STATIC variable Is Nothing, re-initialize. Here's the function I use for caching a reference to the local database:
Public Function dbLocal(Optional bolInitialize As Boolean = True) +
As DAO.Database
' 2003/02/08 DWF added comments to explain it to myself!
' 2005/03/18 DWF changed to use Static variable instead
' uses GoTos instead of If/Then because:
' error of dbCurrent not being Nothing but dbCurrent being closed (3420)
' would then be jumping back into the middle of an If/Then statement
On Error GoTo errHandler
Static dbCurrent As DAO.Database
Dim strTest As String
If Not bolInitialize Then GoTo closeDB
retryDB:
If dbCurrent Is Nothing Then
Set dbCurrent = CurrentDb()
End If
' now that we know the db variable is not Nothing, test if it's Open
strTest = dbCurrent.Name
exitRoutine:
Set dbLocal = dbCurrent
Exit Function
closeDB:
If Not (dbCurrent Is Nothing) Then
Set dbCurrent = Nothing
End If
GoTo exitRoutine
errHandler:
Select Case err.Number
Case 3420 ' Object invalid or no longer set.
Set dbCurrent = Nothing
If bolInitialize Then
Resume retryDB
Else
Resume closeDB
End If
Case Else
MsgBox err.Number & ": " & err.Description, vbExclamation, "Error in dbLocal()"
Resume exitRoutine
End Select
End Function
Anywhere you'd either of these in code:
Dim db As DAO.Database
Set db = CurrentDB()
Set db = DBEngine(0)(0)
db.Execute "[SQL DML]", dbFailOnError
...you can replace the whole thing with:
dbLocal.Execute "[SQL DML]", dbFailOnError
...and you don't have to worry about initializing it when your app opens, or after a code reset -- it's self-healing because it checks the Static internal variable and re-initializes if needed.
The only caveat is that you need to make a call with the bolInitialize argument set to False when you shut down your app, as this cleans up the reference so there's no risk of your app hanging when it goes out of scope as the app closes.
For the other problem, I really doubt there's any solution within VBA, unless you can make an API call and kill the external process. But that's something of a longshot, I think.