Ok So I have some questions about VBA Syntax to Build Access Report.
I know of the following functions to insert information CreateReportControl Function, and to delete information DeleteReportControl, and even to modify sections using section
EX: rpt.section(acDetail).Height=0.
I want to know how to toggle off the page header and footer, not just set Visable to False. I want it so it cannot be seen in Design View.
How to duplicate a report in Vba and assign it a new name / add a Sub Report to a main report and move it's placement around. Or at lease duplicate it and leave it open because I have a code the renames it as shown here:
Public Function GetUniqueReportName() As String
Dim intCounter As Integer
Dim blnIsUnique As Boolean
Dim rpt As Object
For intCounter = 1 To 256
GetUniqueReportName = "SubReport_" & Format(intCounter, "0000")
blnIsUnique = True
For Each rpt In CurrentProject.AllReports
If rpt.Name = GetUniqueReportName Then blnIsUnique = False
Next
If blnIsUnique Then Exit Function
Next
GetUniqueReportName = ""
End Function
Also is there any more functions that would help me build a Access report via VBA, you don't have to explain what they do I just want to know what they are so I can search for them directly because there is not a lot of information on the web on how to do this.
All this information would be of much help, and I am under the assumption that many other people could use this information as well as there is not a lot on this subject. Thanks in advance! :) If you cant answer all the questions that's ok any information at this point is a bonus.
III. This will create rptTmp by calling rptCreateTmpReportSimple():
Function rptCreateTmpReportSimple()
'
Dim lLeft As Long, lTop As Long, lWidth As Long, lHeight As Long
'
Dim strFld As String, strRecordSource As String
Dim strRpt As String
'
Dim rpt As Access.Report
Dim ctl As Access.control
'
strRecordSource = "MyTableName"
strRpt = "rptTmp"
'
Set rpt = Application.CreateReport
'
rpt.visible = False
'
' define report properties, unit is in twips.
'
rpt.RecordSource = strRecordSource
rpt.caption = "Special Report"
'
rpt.DefaultView = acPreview
rpt.Width = 9870
'
rpt.visible = True
'
' save it with a name:
'
DoCmd.Save acDefault, strRpt
'
' create a textbox for each field:
'
strFld = "FieldName1"
lLeft = 100
lTop = 50
lWidth = 2000
lHeight = 250
'
Set ctl = Application.CreateReportControl(strRpt, acTextBox, _
acDetail, , strFld, lLeft, lTop, lWidth, lHeight)
ctl.Name = strFld
ctl.Fontsize = 8
'
' Create other controls...
'...
'
DoCmd.Close acReport, strRpt, acSaveYes
'
' close ADO objects:
'
Set ctl = Nothing
Set rpt = Nothing
'
rptCreateTmpReportSimple = strRpt
'
End Function
For your question:
I. You cannot delete page header and footer, but you can shrink them to 0 height.
II. Copy rptTmp to rptTmp2:
DoCmd.CopyObject , "rptTmp2", acReport, "rptTmp"
Related
Trying to programmatically open a browser from VBA (success) and then close it again using the handle (where I am stuck).
I found this post:
Access VBA to Close a Chrome window opened via Shell
but it does not seem to be working the way I expected. It opens each URL in a new window (and I would rather have all URLs opened in the same window. So I split the code up into two subroutines (see bottom of post).
I am passing the saved pHandle to "StopProcess", but the objLest.Count is always zero. What am I missing here? Thanks.
----------------------- 8< -----------------------------
Sub LaunchProcess(sCommandString, pHandle)
pHandle = Shell(sCommandString)
End Sub
and
Sub StopProcess(pHandle)
' Note: Shell pass the Process Handle to the PID variable
Dim objWMIcimv2 As Object
Dim objProcess As Object
Dim objList As Object
Dim ProcToTerminate As String
Dim intError As Integer
Set objWMIcimv2 = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\.\root\cimv2")
Set objList = objWMIcimv2.ExecQuery("select * from win32_process where Handle='" & CStr(pHandle) & "'")
'
' ObjList contains the list of all process matching the Handle (normally your chrome App, if running)
'
If objList.Count = 0 Then ' <---------- THIS is always 0 so it never closes anything
' No matching Process
' Set all objects to nothing
Set objWMIcimv2 = Nothing
Set objList = Nothing
Set objProcess = Nothing
Exit Sub
Else
'
' Parse all matching Processes
'
For Each objProcess In objList
' additionally check with actual user
colProperties = objProcess.getowner(strNameofUser, strUserdomain)
If strUserdomain + "\" + strNameofUser = Environ$("userdomain") + "\" + Environ$("username") Then
intError = objProcess.Terminate
If intError <> 0 Then
'
' Trap Error or do nothing if code run unattended
'
Else
' Confirm that process is killed or nothing if code run unattended
End If
End If
Next
Set objWMIcimv2 = Nothing
Set objList = Nothing
Set objProcess = Nothing
End If
End Sub
Summary:
I have a need to modify several reports (300+). The toolbar property no longer exists in this new database (was a port of an ADP, have a custom ribbon to replace it), so I need the modify the Toolbar property in all my reports to be blank
What I've tried
Iterating through all reports and changing the property to a string. I've been this with success with essentially the same code to modify a RecordSource and it worked.
Sub RemoveToolbarItemFromReports()
Dim oReport As Report
Dim nItem As Long
Dim bIsLoaded As Boolean
Dim n As Integer
n = FreeFile()
Open "pathToMystuff\test.txt" For Output As #n
For nItem = 0 To CurrentProject.AllReports.Count - 1
bIsLoaded = CurrentProject.AllReports(nItem).IsLoaded
DoCmd.OpenReport CurrentProject.AllReports(nItem).Name, acDesign
Set oReport = Reports(CurrentProject.AllReports(nItem).Name)
If (oReport.Toolbar = "MYTOOLBAR") Then
Debug.Print (oReport.Name)
Debug.Print (oReport.Toolbar)
Write #n, oReport.Name
Write #n, oReport.Toolbar
' Does not persist
Reports(oReport.Name).Toolbar = ""
' Does not persist
Reports(oReport.Name).Report.Toolbar = ""
' Does not persist
oReport.Toolbar = ""
End If
If Not bIsLoaded Then
DoCmd.Close acReport, oReport.Name
End If
Next
Close #n
End Sub
What would be great is if I could either get this VBA code to write changes to my accdb file or if there was a .NET library for reading/modifying Access forms/reports (All libraries that I can find are for reading/writing tables)
I admit to being a bit of a novice, but have designed myself a very handy personal MS Access database. I have tried to find a solution to the following on the net, but have been unsuccessful so far, hence my post (the first time I've done this).
I have a marquee on a form in MS Access, which scrolls the count of "incomplete tasks" to do. A "Tasks COUNT Query" provides a number from zero upwards. After the form loads, the code below scrolls a message (right to left) on the marquee in the form "There are X tasks requiring action." X is the number provided from the "Tasks COUNT Query". I would like the text string on the marquee to update on each loop, so that when I mark a task as complete, the next pass on the marquee shows the number (X) as being the updated count.
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim Number As String
Set db = CurrentDb
Set rst = db.OpenRecordset("Tasks COUNT Query")
If Not (rst.EOF And rst.BOF) Then
Do While Not rst.EOF
Number = rst![Tasks]
strTxt = strTxt & "There are " & Number & " tasks requiring action."
rst.MoveNext
Loop
End If
rst.Close
strTxt = Left(strTxt, Len(strTxt)) 'remove the coma at the end
strTxt = Space(30) & strTxt 'start position
Set rst = Nothing
Set db = Nothing
Me.TimerInterval = 180
End Sub
The following code runs on the form timer interval:
Private Sub Form_Timer()
Dim x
On Error GoTo Form_Timer_Err
x = Left(strTxt, 1)
strTxt = Right(strTxt, Len(strTxt) - 1)
strTxt = strTxt & x
lblMarqTask.Caption = Left(strTxt, 180)
Exit Sub
Form_Timer_Exit:
Exit Sub
Form_Timer_Err:
Me.TimerInterval = 0
Exit Sub
End Sub
I would be grateful for any assistance :)
To answer you question: -
I would like the text string on the marquee to update on each loop
To do this you need to place your code that collects the string into its own procedure and then pick a time to call it. I.e.
Move the Form_Load() code into its own procedure
Private Sub GetString()
Dim db As DAO.Database
... [The remaining code] ...
Me.TimerInterval = 180
End Sub
Change Form_Load() to call the new procedure
Private Sub Form_Load()
GetString
End Sub
Have the timer call the new procedure every so often to update the marquee (also known as ticker tape).
Private Sub Form_Timer()
Dim x
Static LngTimes As Long
On Error GoTo Form_Timer_Err
LngTimes = LngTimes + 1
If LngTimes = 100 Then
GetString
LngTimes = 0
End If
x = Left(StrTxt, 1)
StrTxt = Right(StrTxt, Len(StrTxt) - 1)
StrTxt = StrTxt & x
lblMarqTask.Caption = Left(StrTxt, 180)
Exit Sub
Form_Timer_Exit:
Exit Sub
This will update it every 100 times the timer runs. I have tested this and it works, albeit causing a judder in marquee scrolling.
I would like to take the time to give you some extra support in your code that may help understand VBA and make things clearer/easier for you in any future development.
The changes I have supplied are minimal to give you the desired result within the code you have currently. However it does mean I carried some issue across with it. I would perform the same feature with the below: -
Option Compare Database
Option Explicit
Private StrStatus As String
Private Sub GetStatus()
Dim Rs As DAO.Recordset
Set Rs = CurrentDb.OpenRecordset("SELECT count([Task]) FROM [TblTasks] WHERE [Done] = 'No'")
StrStatus = "There are " & Rs(0) & " tasks requiring action."
Rs.Close
Set Rs = Nothing
End Sub
Private Sub Form_Load()
Me.TimerInterval = 180
Me.lblMarqTask.Caption = ""
End Sub
Private Sub Form_Timer()
Static StrStatus_Lcl As String
If StrStatus_Lcl = "" Then
GetStatus
StrStatus_Lcl = StrStatus & Space(30)
If Me.lblMarqTask.Caption = "" Then Me.lblMarqTask.Caption = Space(Len(StrStatus_Lcl))
End If
Me.lblMarqTask.Caption = Right(Me.lblMarqTask.Caption, Len(Me.lblMarqTask.Caption) - 1) & Left(StrStatus_Lcl, 1)
StrStatus_Lcl = Right(StrStatus_Lcl, Len(StrStatus_Lcl) - 1)
End Sub
The result is the string scrolling will remain smooth the value get updates with each iteration.
To talk through what I have done here.
'Option Explicit' Is always good practice to have at the top of your modules/code, it forces you to declare your variables which can save you a headache in the future. This can be automatically added with new code object by enabling 'Require Variable Declaration' in 'Tools' > 'Options' of the VBA Developer environment (also known as the VBE).
Its not clear what the query was doing but to save on a loop I change it to return a single value that I could use. SELECT count([Task]) FROM [TblTasks] WHERE [Done] = 'No' will return a count of all items in TblTasks where the column Done equals No.
In format load I set the timer interval as this only needs setting once and I also ensured the marquee was empty before it run.
The timer keeps a local copy of the status that it remembers. Declaring with the word Static means the content of the variable is not lost between executions in the way a Dim declared variable would be.
If the local copy is empty (i.e. we have used it all up) then update what the status is (GetStatus) and get a new copy.
I hope this has been of help!
I was trying to follow the instructions on this Microsoft post: http://support.microsoft.com/default.aspx?scid=kb;en-us;209891
to create an organization hierarchy chart based on a self referencing table, just like the example. I keep getting an error that the variable is undefined and VBA points to the line "Optional varReportToID As Variant", but the instructions say not to supply anything for this parameter. Is there anything I can do to make the code run? I didn't change the code, I changed the naming on my tables to match the variables listed. I am working in MS Access 2010, but the example is for an older version of MS.
Thanks!
Option Explicit
'=================Load Event for the Form=======================
'Initiates the routine to fill the TreeView control
'============================================================
Public Sub Form_Load()
Const strTableQueryName = "Employees"
Dim db As DAO.Database, rst As DAO.Recordset
Set db = CurrentDb
Set rst = db.OpenRecordset(strTableQueryName, dbOpenDynaset, dbReadOnly)
AddBranch rst:=rst, strPointerField:="ReportsTo", strIDField:="EmployeeID", strTextField:="LastName"
End Sub
'================= AddBranch Sub Procedure ======================
' Recursive Procedure to add branches to TreeView Control
'Requires:
' ActiveX Control: TreeView Control
' Name: xTree
'Parameters:
' rst: Self-referencing Recordset containing the data
' strPointerField: Name of field pointing to parent's primary key
' strIDField: Name of parent's primary key field
' strTextField: Name of field containing text to be displayed
'=============================================================
Sub AddBranch(rst As Recordset, strPointerField As String, _
strIDField As String, strTextField As String, _
Optional varReportToID As Variant)
On Error GoTo errAddBranch
Dim nodCurrent As Node, objTree As TreeView
Dim strCriteria As String, strText As String, strKey As String
Dim nodParent As Node, bk As String
Set objTree = Me!xTree.Object
If IsMissing(varReportToID) Then ' Root Branch.
strCriteria = strPointerField & " Is Null"
Else ' Search for records pointing to parent.
strCriteria = BuildCriteria(strPointerField, _
rst.Fields(strPointerField).Type, "=" & varReportToID)
Set nodParent = objTree.Nodes("a" & varReportToID)
End If
' Find the first emp to report to the boss node.
rst.FindFirst strCriteria
Do Until rst.NoMatch
' Create a string with LastName.
strText = rst(strTextField)
strKey = "a" & rst(strIDField)
If Not IsMissing(varReportToID) Then 'add new node to the parent
Set nodCurrent = objTree.Nodes.Add(nodParent, tvwChild, strKey, strText)
Else ' Add new node to the root.
Set nodCurrent = objTree.Nodes.Add(, , strKey, strText)
End If
' Save your place in the recordset so we can pass by ref for speed.
bk = rst.Bookmark
' Add employees who report to this node.
AddBranch rst, strPointerField, strIDField, strTextField, rst(strIDField)
rst.Bookmark = bk ' Return to last place and continue search.
rst.FindNext strCriteria ' Find next employee.
Loop
'--------------------------Error Trapping --------------------------
errAddBranch:
MsgBox "Can't add child: " & Err.Description, vbCritical, "AddBranch Error:"
Resume exitAddBranch
End Sub
I have a bunch of mail merge templates setup, when I merge the documents I want to split the results into separate files each one with a name based on the merge field “FileNumber”.
The code I have currently is:
Sub splitter()
' Based on a macro by Doug Robbins to save each letter created by a mailmerge as a separate file.
' With help from http://www.productivitytalk.com/forums/topic/3927-visual-basic-question-for-merge-fields/
Dim i As Integer
Dim Source As Document
Dim Target As Document
Dim Letter As Range
Dim oField As Field
Dim FileNum As String
Set Source = ActiveDocument
For i = 1 To Source.Sections.Count
Set Letter = Source.Sections(i).Range
Letter.End = Letter.End - 1
For Each oField In Letter.Fields
If oField.Type = wdFieldMergeField Then
If InStr(oField.Code.Text, "FileNumber") > 0 Then
'get the result and store it the FileNum variable
FileNum = oField.Result
End If
End If
Next oField
Set Target = Documents.Add
Target.Range = Letter
Target.SaveAs FileName:="C:\Temp\Letter" & FileNum
Target.Close
Next i
End Sub
The problem is if I “Merge to new document” then the “FileNumber” field no longer exists so it can’t pick that up but if I just go to “Preview Results” and run the macro it only saves the currently previewed record and not the rest of the letters.
I’m assuming I need to change the code to something like
For i = 1 To Source.MergedRecord.Count
Set Letter = Source.MergedRecord(i).Range
but I can't work out the correct syntax.
I am aware of http://www.gmayor.com/individual_merge_letters.htm but I don't want the dialog boxes I just want a one click button.
In the Mail merge template document, paste the following macro code in "ThisDocument" module:
Dim WithEvents wdapp As Application
Dim bCustomProcessing As Boolean
Private Sub Document_Open()
Set wdapp = Application
bCustomProcessing = False
ThisDocument.MailMerge.DataSource.ActiveRecord = 1
ThisDocument.MailMerge.ShowWizard 1
With ActiveDocument.MailMerge
If .MainDocumentType = wdFormLetters Then
.ShowSendToCustom = "Custom Letter Processing"
End If
End With
End Sub
Private Sub wdapp_MailMergeWizardSendToCustom(ByVal Doc As Document)
bCustomProcessing = True
Doc.MailMerge.Destination = wdSendToNewDocument
With Doc.MailMerge
For rec = 1 To .DataSource.RecordCount
.DataSource.ActiveRecord = rec
.DataSource.FirstRecord = rec
.DataSource.LastRecord = rec
.Execute
Next
End With
MsgBox "Merge Finished"
End Sub
Private Sub wdapp_MailMergeAfterMerge(ByVal Doc As Document, ByVal DocResult As Document)
If bCustomProcessing = True Then
With Doc.MailMerge.DataSource.DataFields
sFirmFileName = .Item(1).Value ' First Column of the data - CHANGE
End With
DocResult.SaveAs "c:\path\" & sFirmFileName & ".docx", wdFormatXMLDocument
' Path and File Name to save. can use other formats like wdFormatPDF too
DocResult.Close False
End If
End Sub
Remember to update the column number to use for file names, and the path to save the generated files.
After writing this code, save and close the merge template doc. Re-open the file and this time you will be prompted with the Merge wizard. Proceed as required for the Letter, and at the last step, select "Custom Letter Processing" option instead of finishing merge. This will save the separate merged docs in specified folder.
Please remember that this code can be heavy on the processor.
There is a simple solution not involving splitting the resulting document:
Prepare the merge and staying in the template document.Record a macro as you merge one record, then save and close the resulting file, eventuallye advance to the next record.
See the generated macro below. I have added very little code just to extract the filename from a field in the datasource (which is accessible in the template document).
Assign the macro to a shortcut key or implement a loop in VBA. Observe that the fieldnames are casesensitive.
Regards,
Søren
Sub flet1()
'
' flet1 Makro
' 1) Merges active record and saves the resulting document named by the datafield FileName"
' 2) Closes the resulting document, and (assuming that we return to the template)
' 3) advances to the next record in the datasource
'
'Søren Francis 6/7-2013
Dim DokName As String 'ADDED CODE
With ActiveDocument.MailMerge
.Destination = wdSendToNewDocument
.SuppressBlankLines = True
With .DataSource
.FirstRecord = ActiveDocument.MailMerge.DataSource.ActiveRecord
.LastRecord = ActiveDocument.MailMerge.DataSource.ActiveRecord
' Remember the wanted documentname
DokName = .DataFields("FileName").Value ' ADDED CODE
End With
' Merge the active record
.Execute Pause:=False
End With
' Save then resulting document. NOTICE MODIFIED filename
ActiveDocument.SaveAs2 FileName:="C:\Temp\" + DokName + ".docx", FileFormat:= _
wdFormatXMLDocument, LockComments:=False, Password:="", AddToRecentFiles _
:=True, WritePassword:="", ReadOnlyRecommended:=False, EmbedTrueTypeFonts _
:=False, SaveNativePictureFormat:=False, SaveFormsData:=False, _
SaveAsAOCELetter:=False, CompatibilityMode:=14
' Close the resulting document
ActiveWindow.Close
' Now, back in the template document, advance to next record
ActiveDocument.MailMerge.DataSource.ActiveRecord = wdNextRecord
End Sub
Thanks for that roryspop,
I ended up swapping the for loop with
Set Source = ActiveDocument
'The for loop was "To ActiveDocument.MailMerge.DataSource.RecordCount" but for
'some reason RecordCount returned -1 every time, so I set ActiveRecord
'to wdLastRecord and then use that in the for loop.
ActiveDocument.MailMerge.DataSource.ActiveRecord = wdLastRecord
For i = 1 To ActiveDocument.MailMerge.DataSource.ActiveRecord
ActiveDocument.MailMerge.DataSource.ActiveRecord = i
Set Letter = Source.Range
For Each oField In Letter.Fields
The rest of the code is the same, it's not very neat and I'm sure there must be a better way of doing things but it works.
The accepted solution did not work for me. I am using Word 2010. I managed to get a solution working and would like to share it here, so others can benefit from it:
'purpose: save each letter generated after mail merge in a separate file
' with the file name equal to first line of the letter.
'
'1. Before you run a mail merge make sure that in the main document you will
' end your letter with a Section Break (this can be found under
' Page Layout/Breaks/Section Break Next Page)
'2. Furthermore the first line of your letter contains the proposed file name
' and put an enter after it. Make the font of the filename white, to make it
' is invisible to the receiver of the letter. You can also include a folder
' name if you like.
'3. Run the mail merge as usual. A file which contains all the letters is
' generated.
'4. Add this module to the generated mail merge file. Use Alt-F11 to go to the
' visual basic user interface, right click in the left pane on the generated
' file and click on Import File and import this file
'5. save the generate file with all the letters as ‘Word Macro Enabled doc
' (*.docm)’.
'6. close the file.
'7. open the file again, click allow content when a warning about macro's is
' shown.
'8. execute the macro with the name SaveRecsAsFiles
Sub SaveRecsAsFiles()
' Convert all sections to Subdocs
AllSectionsToSubDoc ActiveDocument
'Save each Subdoc as a separate file
SaveAllSubDocs ActiveDocument
End Sub
Private Sub AllSectionsToSubDoc(ByRef doc As Word.Document)
Dim secCounter As Long
Dim NrSecs As Long
NrSecs = doc.Sections.Count
'Start from the end because creating
'Subdocs inserts additional sections
For secCounter = NrSecs - 1 To 1 Step -1
doc.Subdocuments.AddFromRange _
doc.Sections(secCounter).Range
Next secCounter
End Sub
Private Sub SaveAllSubDocs(ByRef doc As Word.Document)
Dim subdoc As Word.Subdocument
Dim newdoc As Word.Document
Dim docCounter As Long
Dim strContent As String, strFileName As String
docCounter = 1
'Must be in MasterView to work with
'Subdocs as separate files
doc.ActiveWindow.View = wdMasterView
For Each subdoc In doc.Subdocuments
Set newdoc = subdoc.Open
'retrieve file name from first line of letter.
strContent = newdoc.Range.Text
strFileName = Mid(strContent, 1, InStr(strContent, Chr(13)) - 1)
'Remove NextPage section breaks
'originating from mailmerge
RemoveAllSectionBreaks newdoc
With newdoc
.SaveAs FileName:=strFileName
.Close
End With
docCounter = docCounter + 1
Next subdoc
End Sub
Private Sub RemoveAllSectionBreaks(doc As Word.Document)
With doc.Range.Find
.ClearFormatting
.Text = "^b"
With .Replacement
.ClearFormatting
.Text = ""
End With
.Execute Replace:=wdReplaceAll
End With
End Sub
Part of the code I copied from here