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
Related
I don't use VBA from long time....I have this form in Access 2016
When I try to access to the various TextBoxes through the Me.Controls Collection and convert it to a TextBox object, I get a Null reference but some its properties are valid (eg. tb.Name)
Private Sub Form_Load()
Dim ctrl As Control
Dim tb As TextBox
Dim evTb As clsEventTextBox
Set m_TbColl = New Collection
For Each ctrl In Me.Controls
If Left$(ctrl.Name, 4) = "Txt_" Then
Set tb = ctrl
'Create the TextBox wrapper
Set evTb = New clsEventTextBox
Set evTb.EventsHandler = Me
Set evTb.InnerTextBox = tb <----- HERE tb Is NULL
m_TbColl.Add evTb, ctrl.Name
End If
Next
End Sub
I miss something?
Also, is there a way to get the Type of a Control instead of using
Left$(ctrl.Name, 4) = "Txt_"
To get the type, use TypeName like this:
If TypeName(ctrl) = "TextBox" Then
And to ensure tb takes the form of a Textbox object, use this
Set tb = Controls(ctrl.Name)
You haven't shown the class that you're using, but assuming it looks something like this:
Private WithEvents f_EH As Access.Form
Private WithEvents f_TB As Access.TextBox
Public Property Set EventsHandler(frm As Access.Form)
Set f_EH = frm
End Property
Public Property Set InnerTextBox(ctl As Access.TextBox)
Set f_TB = ctl
End Property
If I use a class with that structure, the code in your post works fine. But notice that I've explicitly set the expected type of the InnerTextBox property to Access.TextBox.
But your code does needless casting, uses Hungarian naming (yuck!), and relies on the first 4 characters of the name being "Txt_" and could be written as:
Dim ctrl As Control
Dim evTb As clsEventTextBox
Set m_TbColl = New Collection
For Each ctrl In Me.Controls
If TypeOf ctrl Is Access.TextBox Then
'Create the TextBox wrapper
Set evTb = New clsEventTextBox
Set evTb.EventsHandler = Me
Set evTb.InnerTextBox = ctrl 'Just pass the ctrl reference without casting
m_TbColl.Add evTb, ctrl.Name
End If
Next
Note the use of TypeOf in If TypeOf ctrl Is Access.TextBox Then to determine whether the control is a TextBox.
As an end of some inserts, via VBA script, I have been doing in an ACCESS table. I have as a requirement to order the table by a field. So a third person would open it via the ACCESS Navigation Pane and it would be shown in the specified order.
EDIT: I also need that table to be writable by this third person.
I can think about creating a new table, using an SQL sentence to order it. But it seems like a very ugly option.
Is there any way to archive it using a DAO object or other VBA approach?
A query which sorts the table is the cleanest solution.
If you don't want to create an extra object for that, you can set the table sorting with DAO properties like this:
Sub DoIt()
Call TableSetSort("myTable", "myField")
End Sub
' Set a table to be sorted by <sFieldname>
Public Sub TableSetSort(sTable As String, sFieldname As String)
Dim DB As Database
Dim tdf As TableDef
Dim prop As DAO.Property
Set DB = CurrentDb
Set tdf = DB.TableDefs(sTable)
' Set field to order by
Call TableAddProperty(tdf, "OrderBy", dbText, sFieldname)
' These two may be true by default, but better safe than sorry
Call TableAddProperty(tdf, "OrderByOn", dbBoolean, True)
Call TableAddProperty(tdf, "OrderByOnLoad", dbBoolean, True)
' if you want to debug
For Each prop In tdf.Properties
Debug.Print prop.Name, prop.Value
Next prop
End Sub
' Set or add a property in a TableDef
Public Sub TableAddProperty(tdf As DAO.TableDef, sName As String, iType As DAO.DataTypeEnum, vValue As Variant)
Dim prop As DAO.Property
' Try to set the property value, this will fail with Runtime Error 3270 if the property doesn't exist
On Error Resume Next
tdf.Properties(sName) = vValue
If Err.Number = 3270 Then
' Property doesn't exist yet - create and append it
On Error GoTo 0
Set prop = tdf.CreateProperty(sName, iType, vValue)
tdf.Properties.Append prop
End If
' Note: error handling here is quite minimal!
End Sub
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
Does MS Access allow to get the recordsource value of the form without opening the form itself? I'm trying to optimize my code as of now, what I did is I just hide the form then get the Recordsource form query but it takes time to load since some of the forms trigger a code upon onload.
I'm late to the game here - I sometimes post answers months or years after the original question was posted, as I post my own solutions when a quick search of the 'Stack finds questions relevant to my own problem of the day, but no answers that I can actually use.
[UPDATE, 06 June 2016]
The 'NameMap' property is not available in document objects from Access 2010 onwards. However, 'Stacker Thunderframe has pointed out that this is now available in the 'MsysNameMap' table.
I have amended the code, and this works in Access 2010 and 2013.
[/UPDATE]
Most of a form's properties are only available when the form is open, but some are available in the form's entry in the DAO Documents collection.
The DAO 'document' is a horrible object: it won't persist in memory and you have to refer to it explicitly every time you use it:
FormName = "MyForm"
For i = 0 To Application.CodeDb.Containers("Forms").Documents(FormName).Properties.Count - 1
Debug.Print i & vbTab & Application.CodeDb.Containers("Forms").Documents(FormName).Properties(i).Name & vbTab & vbTab & Application.CodeDb.Containers("Forms").Documents(FormName).Properties(i).Value
Next
Run that snippet for your form, and you'll see a 'NameMap' property that contains a list of the form's controls, and some of the form's properties.
...In a truly horrible format which needs a binary parser. You might want to stop reading and take an aspirin, right now, before continuing.
Health Warnings:
The NameMap Property is undocumented. It is therefore unsupported and there is no guarantee that this solution will work in future versions of Microsoft Access.
The solution in my code below will stop working if the NameMap's two-byte binary label for a Record Source ever changes, or if it's locale-specific.
This is a horrible hack: I accept no liability for any effects on your sanity.
OK, here's the code:
A VBA function to return the Record Source from a closed MS-Access form:
Private Function FormRecordSource_FromNameMap(FormName As String) As String
' Reads the Record Source from the NameMap Property of the Document object for the form.
' WARNING: there is a potential error here: if the form's RecordSource property is blank
' and it has one or more list controls with a .RecordSource property populating
' the list, this function will return the first list control's Record Source.
' This won't work if you're using non-ASCII characters (Char > 255) in your form name.
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim arrByte() As Byte
Dim strOut As String
If Application.Version < 12 Then
arrByte = Application.CodeDb.Containers("Forms").Documents(FormName).Properties("NameMap").Value
For i = 1 To UBound(arrByte) - 2 Step 2
' 2-byte marker for a querydef in the NameMap:
If (arrByte(i) = 228 And arrByte(i + 1) = 64) Then
j = i + 2
Do While arrByte(j) = 0 And arrByte(j + 1) = 0 And j < UBound(arrByte)
' loop through the null chars between the marker and the start of the string
j = j + 2
Loop
strOut = ""
Do Until (arrByte(j) = 0 And arrByte(j + 1) = 0) Or j >= UBound(arrByte) - 2
If arrByte(j) = 0 Then j = j + 1
' loop until we reach the null char which terminates this string
' appending the Bchars (not unicode Wchars!) of the table or query
strOut = strOut & Chr(arrByte(j))
j = j + 2
Loop
Exit For ' we only want the first datasource
End If
Next i
Else
arrByte = Nz(DLookup("[NameMap]", "[MSYSNameMap]", "[Name] = '" & FormName & "'"), vbNullChar)
If UBound(arrByte) < 4 Then Exit Function
strOut = ""
For j = 60 To UBound(arrByte) - 2 Step 2
If arrByte(j) = 0 And arrByte(j + 1) = 0 Then Exit For
strOut = strOut & Chr(arrByte(j))
Next j
End If
frmRecordSource_FromNameMap = strOut
Erase arrByte
End Function
If you use the RecordSource in (say) OpenRecordset or a DCOUNT function, I would advise you to encapsulate it in square brackets: you might get the name of a hidden query object saved from a 'SELECT' statement in the RecordSource, and that name will contain '~' tilde characters which need special handling.
And now, something extra that you didn't ask for, but other people will be looking for if they Googled their way here for 'MS Access RecordSource for a closed form':
Getting an MS-Access form's RecordSource, whether it's open or not
Most times, your form will be open. Problem is, you don't know that... And if it's a subform, it might not be visible in the Forms() collection. Worse, a form that's hosted as a subform might exist as multiple instances in several open forms.
Good luck with that, if you're looking to extract dynamic properties... Like filters, or the Record Source if it's set 'on the fly' by VBA.
Public Function GetForm(FormName As String, Optional ParentName As String = "") As Form
' Returns a form object, if a form with a name like FormName is open
' FormName can include wildcards.
' Returns Nothing if no matching form is open.
' Enumerates subforms in open forms, and returns the subform .form object if
' it has a matching name. Note that a form may be open as multiple instances
' if more than one subform hosts it; the function returns the first matching
' instance. Specify the named parent form (or the subform control's name) if
' you need to avoid an error arising from multiple instances of the form.
Dim objForm As Access.Form
If ParentName = "" Then
For Each objForm In Forms
If objForm.Name Like FormName Then
Set GetForm = objForm
Exit Function
End If
Next
End If
If GetForm Is Nothing Then
For Each objForm In Forms
Set GetForm = SearchSubForms(objForm, FormName, ParentName)
If Not GetForm Is Nothing Then
Exit For
End If
Next
End If
End Function
Private Function SearchSubForms(objForm As Access.Form, SubFormName As String, Optional ParentName As String = "") As Form
' Returns a Form object with a name like SubFormName, if the named object SubFormName is subform
' of an open form , or can be recursively enumerated as the subform of an open subform.
' This function returns the first matching Form: note that a form can be instantiated in multiple
' instances if it is used by more than one subform control.
Dim objCtrl As Control
For Each objCtrl In objForm
If TypeName(objCtrl) = "SubForm" Then
If objCtrl.Form.Name Like SubFormName Then
If ParentName = "" Or objForm.Name Like ParentName Or objCtrl.Name Like ParentName Then
Set SearchSubForms = objCtrl.Form
Exit For
End If
Else
Set SearchSubForms = SearchSubForms(objCtrl.Form, SubFormName, ParentName)
If Not SearchSubForms Is Nothing Then
Exit For
End If
End If
End If
Next objCtrl
End Function
Public Function FormRecordSource(FormName As String, Optional ParentName As String = "") As String
' Returns the Recordsource for a form, even if it isn't open in the Forms() collection
' This will look for open forms first. If you're looking for a subform, you may need a
' parent name for the form which hosts the subform: your named form might be open as a
' subform instance in more than one parent form.
' WARNING: there is a potential error here: if the form isn't open, and it has a blank
' RecordSource property, and it has one or more controls with a .RecordSource
' property populating a list, a list control's RecordSource could be returned
Dim objForm As Form
If FormName = "" Then
Exit Function
End If
Set objForm = GetForm(FormName, ParentName)
If objForm Is Nothing Then
FormRecordSource = FormRecordSource_FromNameMap(FormName)
Else
FormRecordSource = objForm.RecordSource
Set objForm = Nothing
End If
End Function
Share and enjoy: and please accept my apologies for any unwanted line breaks in the code sample.
One option would be to save the Record Source of the form as a Query. Say you have a form named [AgentForm] whose Record Source is
SELECT ID, AgentName FROM Agents
In your development .accdb copy of the database, open the form in Design View and open the Record Source in the Query Builder. Click the "Save As" button ...
and save the query as "AgentForm_RecordSource". Now the Record Source property of the form is just a reference to the saved query, and the query itself can be accessed directly through a QueryDef object. So, you could retrieve the SQL statement for the form's Record Source with
Dim cdb As DAO.Database, qdf As DAO.QueryDef, sql As String
Set cdb = CurrentDb
Set qdf = cdb.QueryDefs("AgentForm_RecordSource")
sql = qdf.SQL
or you could go ahead and open a Recordset with
Dim cdb As DAO.Database, qdf As DAO.QueryDef, rst As DAO.Recordset
Set cdb = CurrentDb
Set qdf = cdb.QueryDefs("AgentForm_RecordSource")
Set rst = qdf.OpenRecordset
If the form's Record Source is a SELECT statement rather than the name of a table or saved query, you can check the QueryDefs collection for the hidden QueryDef which Access created for that Record Source statement.
If it exists, you can check its .SQL property.
strFormName = "Form15"
? CurrentDb.QueryDefs("~sq_f" & strFormName).SQL
SELECT DISTINCTROW *
FROM [DB Audits];
You can trap error #3265, "Item not found in this collection", which will be thrown if that QueryDef does not exist.
Since you can't open your form in design view and opening your form regularly is causing performance issues, there are but a few more workarounds:
Depending on how you want to check for the closed form's recordsource, you can set a global variable in the following way, in a separate module:
Public glb_getrecordsource As String
Afterwards, depending on how you call the code, you can do the following:
Private Sub Command1_Click()
glb_getrecordsource = "Yes"
DoCmd.OpenForm "Form1"
'... Do something
End Sub
Then, as the final step, put the following at the beginning of your form's OnLoad event:
Private Sub Form_Load()
If glb_getrecordsource = "Yes" Then
glb_getrecordsource = Me.Form.RecordSource
DoCmd.Close acForm, "Form1", acSaveYes
Exit Sub
End If
'... Usual OnLoad events
End Sub
This will at least solve the performance issues, since you will not trigger any of the time consuming events, in the form's load event.
Another workaround:
You can export your form to a .txt file and then search the text file for the recordsource. The following code will export your forms to .txt files in a specified folder:
Dim db As Database
Dim d As Document
Dim c As Container
Dim sExportLocation As String
Set db = CurrentDb()
sExportLocation = "C:\AD\" 'Do not forget the closing back slash! ie: C:\Temp\
Set c = db.Containers("Forms")
For Each d In c.Documents
Application.SaveAsText acForm, d.Name, sExportLocation & "Form_" & d.Name & ".txt"
Next d
Code partly borrowed from this forum. Afterwards, you only have to open the file and search for the recordsource. If the recordsource is empty it will not be exported, so keep that in mind. Also, I doubt this will improve perfomance, but who knows!
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.