Closing the last report in a collection - vba

I made a collection which stores multiple instances of a report being opened with different filters.
If I try to close the most recently opened report, it is removed from the collection but does not close.
All other reports are both removed from the collection and closed.
How can I close the last report?
Private Sub CloseButton_Click()
Dim i As Integer
Dim rptcol As New collection
Set rptcol = ReportCollectionModule.rptCollection
For i = 1 To rptcol.Count
If Me.Hwnd = rptcol.Item(i).Hwnd Then
Exit For
End If
Next
DoCmd.Close acReport, rptcol.Item(i).Caption, acSaveNo
rptcol.Remove i
Set ReportCollectionModule.rptCollection = rptcol
End Sub
The ReportCollectionModule is a basic setter and getter.
Option Compare Database
Private myRptCollection As New collection
Public Property Get rptCollection() As collection
Set rptCollection = myRptCollection
End Property
Public Property Set rptCollection(thiscollection As collection)
Set myRptCollection = thiscollection
End Property
The reports are added to the collection as follows:
Private Sub ID_Click()
Dim rpt As Report
Dim rptcol As New collection
Set rptcol = ReportCollectionModule.rptCollection
Set rpt = New Report_ProductTable
rpt.RecordSource = "Product Table"
rpt.Filter = "[ID]= " & Me![ID]
rpt.Visible = True
rpt.Caption = DLookup("[ProductName]", "Product Table", "[ID] = " & Me![ID])
rpt.Requery
rptcol.Add rpt, CStr(rpt.Hwnd)
Set ReportCollectionModule.rptCollection = rptcol
Product_Name.SetFocus
ID.Visible = False
End Sub

The line DoCmd.Close acReport, rptcol.Item(i).Caption, acSaveNo will probably not work as you intented because the Docmd.Close object, objectName will close the object by it's name not by it's caption/title.
If you close the report by it's Name using docmd.close object, object name, the first instance that Access can find in the memory is closed until no more to close.
ReportCollectionModule.rptCollection.Remove report.Hwnd should close the instance you specify. Please post how you are managing/adding forms, you may have a flaw there.
Instead of using a class, make rptCollection as a public object/dictionary and just use rptCollection.remove hwnd

This is what I am using. It closes all forms but the one which it is calling from:
Private Sub CloseAllForms()
Dim lngLoop As Long
On Error Resume Next
For lngLoop = (Forms.Count - 1) To 1 Step -1
If Me.Name <> Forms(lngLoop).Name Then
DoCmd.Close acForm, Forms(lngLoop).Name
End If
Next lngLoop
End Sub
You can do the same for reports:
Private Sub CloseAllReports()
Dim lngLoop As Long
On Error Resume Next
For lngLoop = (Reports.Count - 1) To 1 Step -1
If Me.Name <> Reports(lngLoop).Name Then
DoCmd.Close acReport, Reports(lngLoop).Name
End If
Next lngLoop
End Sub

Related

I get a compile error: variable not defined in backend, same code works in an exact copy on frontend

I was following a guide on how to make a basic login form in access vba, however today when i started up my work pc, the login doesn't seem to be working at all in my back end (from which i originally exported the login form to my front end). It gave me a "User defined type not defined" error pointing to "Private Sub btnLogin_Click()" but that's no longer the case, now it randomly without making any changes is giving me a "compile error: variable not defined" pointing to "Private Sub btnLogin_Click()" and highlighting "dbOpenSnapshot"
here's the entire code from my backend file which gives me the error
Option Compare Database
Option Explicit
Private Sub btnLogin_Click()
Dim rs As Recordset
Set rs = CurrentDb.OpenRecordset("BElogon", dbOpenSnapshot, dbReadOnly)
rs.FindFirst "logon_user='" & Me.txtboxname & "'"
If rs.NoMatch = True Then
Me.txtwrongname.Visible = True
Me.txtboxname.SetFocus
Exit Sub
End If
Me.txtwrongname.Visible = False
If rs!logon_pass <> Me.txtboxpass Then
Me.txtwrongpass.Visible = True
Me.txtboxpass.SetFocus
Exit Sub
End If
Me.txtwrongpass.Visible = False
DoCmd.OpenForm "FEindex"
DoCmd.Close acForm, Me.Name
End Sub
and here's the version in my front end which works flawlessly
Option Compare Database
Option Explicit
Private Sub btnLogin_Click()
Dim rs As Recordset
Set rs = CurrentDb.OpenRecordset("BElogon", dbOpenSnapshot, dbReadOnly)
rs.FindFirst "logon_user='" & Me.txtboxname & "'"
If rs.NoMatch = True Then
Me.txtwrongname.Visible = True
Me.txtboxname.SetFocus
Exit Sub
End If
Me.txtwrongname.Visible = False
If rs!logon_pass <> Me.txtboxpass Then
Me.txtwrongpass.Visible = True
Me.txtboxpass.SetFocus
Exit Sub
End If
Me.txtwrongpass.Visible = False
DoCmd.OpenForm "FEindex"
DoCmd.Close acForm, Me.Name
End Sub
i have found that it might be a missing reference however my front end doesn't have them enabled either and it works, so i'm just trying to understand this..
If the suggestions to Decompile the database didn't work, it may be the use of dbOpenSnapshot or dbReadOnly. Instead, try:
Set rs=CurrentDb.OpenRecordset("BElogon")
Also, rather than opening the table with all records selected, and then trying to find a match, it is faster to open a recordset that is already filtered:
Set rs=CurrentDb.OpenRecordset("SELECT logon_pass FROM BElogon WHERE logon_user='" & Me!txtboxname & "'")
If Not (rs.BOF And rs.EOF) Then ' have found a matching user
If rs!logon_pass<>Me!txtboxpass Then ' password supplied does not match that stored for the user
End If
Else ' user name not found
End If
Regards,

Error when saving individual docs from a mail merge

I Have been using a VBA code to individually save all letters separately from a mail merge into a designated folder. It has always worked previously howver with the document I am trying to do it for now it is onyl saving the first document and then coming up with an error stating:
run-time error '5825' object has been deleted
When I go to debug it highlights the line near the bottom reading 'DocResult.Close False'
How can I fix this?
Tried changing this to True or deleting line entirely but does not fix problem. Each document is quite large so takes approx 30 seconds to save
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(44).Value ' First Column of the data - CHANGE
End With
DocResult.SaveAs "xxx\" & sFirmFileName & ".doc", wdFormatDocument
' Path and File Name to save. can use other formats like wdFormatPDF too
DocResult.Close False
End If
End Sub
You have to set your object as nothing like this :
Set DocResult = nothing

Calling Control from another Form Gets Error: Method or Data member not Found

I'm encountering error Method or data member not found when I compile my project. button control "btnViewUpdateRefresh" exists in other form.
Private Sub UserForm_Terminate()
Dim oSection As Word.Section
Dim oRange As Word.Range
Dim var
If Documents("PART V1.0.docm").ProtectionType <> wdNoProtection Then
Documents("PART V1.0.docm").Unprotect "1234"
Else
End If
ThisDocument.Activate
ThisDocument.StoryRanges(wdMainTextStory).Delete
MainWindow.Show
Call MainWindow.btnViewUpdateRefresh_Click --> error points in here.
If Documents("PART V1.0.docm").ProtectionType <> wdNoProtection Then
Else
Documents("PART V1.0.docm").Protect wdAllowOnlyFormFields, True, "1234"
End If
End Sub

Speech in PowerPoint (VBA)

I want to be able to make PowerPoint speak, say something.
I tried this code to make PP speak:
Private Sub CommandButton1_Click()
Application.Speech.Speak "Hello World"
End Sub
But the code doesn't work, it doesn't exists. What can I do, which is the right code?
It says:
Compile Error Method or Data member not found.
Sorry for any error on my question.
The reason it's not working is that there's no Application.Speech property/method in the PPT object model. Somewhere or other I've seen code that invokes Excel to do the lifting but here's an answer from John Wilson of PPT Alchemy that seems more direct:
There is a page on our site about talking message boxes
http://www.pptalchemy.co.uk/PowerPoint_speech.html
It could be easily modified to speak the text in a shape in Slide Show mode.
Sub speak(oshp As Shape)
Dim strSpeak As String
Dim SAPIObj As Object
Set SAPIObj = CreateObject("SAPI.SPvoice")
SAPIObj.Rate = -2
If oshp.HasTextFrame Then
If oshp.TextFrame.HasText Then
strSpeak = oshp.TextFrame.TextRange.Text
End If
End If
SAPIObj.speak "<pitch middle='-15'>" & strSpeak
End Sub
If you don't need to pick up the text from a particular shape, a modification along these lines should do it:
Sub SayThisAloud(sText as String)
Dim SAPIObj As Object
Set SAPIObj = CreateObject("SAPI.SPvoice")
SAPIObj.Rate = -2
SAPIObj.speak "<pitch middle='-15'>" & sText
End Sub
REgarding the MSDN page, it'll take some editing/modification to convert it to something a bit more useful. For example, here's an example of how you can get a list of the voices available:
In the declarations section:
Private V As Object
Private T As Object
Then
Sub ListVoices()
On Error GoTo EH
Dim strVoice As String
Dim SAPIObj As Object
Set SAPIObj = CreateObject("SAPI.SPvoice")
'Get each token in the collection returned by GetVoices
For Each T In SAPIObj.GetVoices
strVoice = T.GetDescription 'The token's name
'List1.AddItem strVoice 'Add to listbox
Debug.Print strVoice
Next
Exit Sub
EH:
If Err.Number Then ShowErrMsg
End Sub
Private Sub ShowErrMsg()
' Declare identifiers:
Dim T As String
T = "Desc: " & Err.Description & vbNewLine
T = T & "Err #: " & Err.Number
MsgBox T, vbExclamation, "Run-Time Error"
End
End Sub

Update MS Access Marquee text on each loop

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!