I have an access report that updates 4 image controls based on a table that is sourcing images from a directory. The report generates a page per record, however the image controls are not changing after page 1 (just showing same images for all the other pages). Appartently, the code worked fine on Windows XP and now does not work on Windows 7 OS (both used Office 07). Here is the code:
Private Sub Report_Current()
UpdateImages
End Sub
Private Sub Report_Load()
UpdateImages
End Sub
Private Sub Report_Page()
UpdateImages
End Sub
Private Sub UpdateImages()
On Error GoTo errHandler
Dim RS As DAO.Recordset
Set RS = CurrentDb.OpenRecordset("SELECT Image_Loc, Image_Name FROM HH_Media WHERE InspectionID = " & CInt(Me.InspectionID.Value) & " ORDER BY MediaID ASC")
If Not RS.BOF And Not RS.EOF Then
Dim i As Integer
For i = 1 To 4
If Not RS.EOF Then
Dim pictureCtrl As Image
Set pictureCtrl = GetControl("Image" & i)
Dim strImage As String
strImage = RS.Fields("Image_Loc").Value & "\" & RS.Fields("Image_Name").Value
If Not pictureCtrl Is Nothing Then
If FileExists(strImage) Then
pictureCtrl.Picture = strImage
SetLabel "lblImage" & i, RS.Fields("Image_Name").Value
Else
pictureCtrl.Picture = ""
SetLabel "lblImage" & i, "Does not exist"
End If
End If
RS.MoveNext
Else
Exit For
End If
Next
End If
RS.Close
Set RS = Nothing
Exit Sub
errHandler:
MsgBox "An error occurred while updating the form display." & vbNewLine & Err.Description, vbApplicationModal + vbCritical + vbDefaultButton1 + vbOKOnly, Me.Name
Resume Next
End Sub
The images do exist in the directory that are referenced in the table. Any ideas of what is missing?
Thank You
Whenever I need to do some dynamic content I always use the [section]_Format event - so if your controls are on the Detail section then:
Private Sub Detail_Format(Cancel As Integer, FormatCount As Integer)
If FormatCount = 1 then 'only need to do this once per record
UpdateImages
Endif
End Sub
I've never seen the GetControl method, and I don't have a lot of experience using the Image control, but it seems like that the Dim statement should read more like:
Dim pictureCtrl as Control
Set pictureCtrl = Me.Controls("Image" & i)
I would insert a break and verify that
strImage = RS.Fields("Image_Loc").Value & "\" & RS.Fields("Image_Name").Value
is returning values you expect. You can also shorten these to:
strImage = rs!Image_Loc & "\" & rs!Image_Name
sometimes Access doesn't like the added ".value" as this is already the default return.
Related
In access 2013, I'm using a form (called "Table1") linked to a table that has an attachment field that I'm adding a scanned document (bmp) to. There are two textboxes that I use to name the scanned document on the form. Everything works if I input the record then hit save and then click my button (called testButton), but if I don't hit save, the attachment saves to the last record or a few records back. I'm pretty sure it's a saving issue, because when I try to scan and attach after the computer has been dormant, it works just fine, I'm guessing autosave. However, if I'm trying to scan multiple records back to back, the attachment goes to the previous record if I don't hit save on the ribbon. I've tried placing
DoCmd.Save acForm, "Table1"
or
If Me.Dirty Then
Me.Dirty = False
MsgBox ("File Saved")
End If
or even
DoCmd.RunCommand acCmdSaveRecord
at different points in the code to no avail. The DoCmd functions save the record, but start the recordset at the first record, not the one I'm currently inputting. So instead of attaching to the second to last record, it attaches to the first record.
My goal is to be able to hit the button and it work perfectly without having to hit save. I even looked for a code that does exactly what the save button on the ribbon does to no avail.
Here is the current code I'm using.
Option Compare Database
Dim FileLocation As String
Dim diagFile As FileDialog
Private Sub testButton_Click()
'DoCmd.Save acForm, "Table1" (Commented out, because it's not working)
Dim db As DAO.Database
Dim rsParent As DAO.Recordset2
Dim rsChild As DAO.Recordset2
If Me.Dirty Then
Me.Dirty = False
MsgBox ("File Saved")
End If
Me.testBox = Me.Payment & Me.Merchant ' I use this to see the input on screen
ScanImage
AttachImage
End Sub
Private Sub ScanImage()
'This method works, it's the AttachImage that's causing issues.
Set diagFile = Application.FileDialog(msoFileDialogSaveAs)
diagFile.Title = "Save Bitmap File As..."
diagFile.InitialFileName = Me.Payment & " " & Me.Merchant & ".bmp"
diagFile.Show
FileLocation = diagFile.SelectedItems(1)
Dim scanDiag As New WIA.CommonDialog
Dim image As WIA.ImageFile
Set image = scanDiag.ShowAcquireImage(ScannerDeviceType)
image.SaveFile FileLocation
MsgBox ("File Saved")
' MsgBox (CurrentRecord) I used this to see on screen the record I'm inputing
End Sub
Private Sub AttachImage()
MsgBox ("Step 1 attaching") ' I use this and all the MsgBoxes to see where in the code the procedure is.
'MsgBox ("Step 2 attaching")
On Error GoTo Err_AddImage
Set db = CurrentDb
Set rsParent = Me.Recordset
rsParent.Edit
Set rsChild = rsParent.Fields("Receipt").Value
'MsgBox "Nombre el archivo: " & rsChild("FileName").Value
'MsgBox "Tipo de archivo: " & rsChild.Fields("FileType").Value
'MsgBox "Data del archivo: " & rsChild.Fields("FileData").Value
'Do Until rsChild.EOF
' For Each fld In rsChild.Fields
' Debug.Print fld
' Next fld
' rsChild.MoveNext
' Loop
'MsgBox ("Step 3 attaching")
rsChild.AddNew
rsChild.Fields("FileData").LoadFromFile ("C:\Users\omoawotona\Desktop\Receipt Clone\" & Me.Payment & " " & Me.Merchant & ".bmp")
rsChild.Update
rsParent.Update
'MsgBox ("Step 4 attaching")
Me.Refresh
'MsgBox ("Step 5 attaching")
'MsgBox ("Attachment done")
Exit_AddImage:
Set rsChild = Nothing
Set rsParent = Nothing
Exit Sub
Err_AddImage:
If Err = 3820 Then
MsgBox ("File already part of the multi-valued field!")
Me.Refresh
Resume Next
Else
MsgBox "Some Other Error occured!", Err.Number, Err.Description
Resume Exit_AddImage
End If
End Sub
I'm using a cmd button to open a popup (single) form that has (2) txtboxes that I want to reflect the information from a single record in the previous (continuous) form. The cmd will
Code on the cmd button:
Private Sub cmdReassign_Click()
On Error GoTo ErrHandler
Dim strOpenArgs As String
strOpenArgs = Me.txtToolGroupID & "," & Me.txtEmployee_Name
DoCmd.OpenForm "popfrmReassignGroupedTools", OpenArgs:=strOpenArgs '
ExitHere:
Exit Sub
ErrHandler:
MsgBox Err.Number & ": " & Err.Description, vbExclamation
Resume ExitHere
End Sub
Code on Form_Open
Private Sub Form_Open(Cancel As Integer)
On Error GoTo ErrHandler
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Dim strSQL As String
Dim strOpenArgs As String
strOpenArgs = Me.OpenArgs
Set dbs = CurrentDb
strSQL = "SELECT * From qryToolReassignment_Grouped Where ToolGroupID=" & Me.txtToolGroupID & ";"
Set rst = dbs.OpenRecordset(strSQL)
If rst.EOF Then
GoTo ExitHere
End If
With Me
.txtToolCategoryQty = rst.Fields("[Quantity]")
.txtToolLocation = rst.Fields("[Employee Name]")
End With
ExitHere:
On Error Resume Next
Set rst = Nothing
Set dbs = Nothing
Exit Sub
ErrHandler:
MsgBox Err.Number & ": " & Err.Description, vbExclamation
Resume ExitHere
End Sub
I'll admit I borrowed the code from a similar setup where the information was called from a cbo not a cmd. When the popup form opens, only the first record in the query is shown not the record associated to the cmd. Any suggestions. TIA.
Not clear to me what you want. If popup form is BOUND and you want it to open to existing record then use the WHERE argument of OpenForm:
DoCmd.OpenForm "popfrmReassignGroupedTools", , , "ToolGroupID=" & Me.txtToolGroupID
If txtToolGroupID is on the continuous form then the popup form Open event cannot reference it with Me alias.
If you want to pass multiple values with OpenArgs, will have to use string manipulation functions to parse the data elements.
If Not IsNull(Me.OpenArgs) Then
intID = Left(Me.OpenArgs, InStr(Me.OpenArgs, ",")-1)
strEmp = Mid(Me.OpenArgs, InStr(Me.OpenArgs, ",")+1)
End If
Instead of all that code to declare and open recordset object, a DLookup() might serve.
In my office of 65 people, I want to create a "portal" for all the employees out of a single .accdb file. It will allow each employee to navigate to a new "screen" from a dropdown menu.
Should I use a single form with plug-and-play subform controls in order to centralize the VBA code, or should I just use different forms?
I'm thinking it would be nice to have one form with plug-and-play subform controls. When the employee selects a new "screen", the VBA just sets the SourceObject property of each subform control and then re-arranges the subforms based on the layout of the selected "screen".
For instance, we currently use a couple of Access database forms to enter and review errors that we find in our workflow system. So in this scenario, to review the errors I would just say
SubForm1.SourceObject = "Form.ErrorCriteria"
SubForm2.SourceObject = "Form.ErrorResults"
And then I would just move them into place (these values would be pulled dynamically based upon the "screen" selected):
SubForm1.Move WindowWidth * 0.05, WindowHeight * 0.05, WindowWidth * 0.9, WindowHeight * 0.2
SubForm2.Move WindowWidth * 0.05, WindowHeight * 0.25, WindowWidth * 0.9, WindowHeight * 0.65
So this creates a small header section (SubForm1) on the form where I can select the criteria for the errors I want to see (data range, which team committed the error, etc) and then I can view the errors in the much larger section below the header (SubForm2) that holds the datasheet with the results.
I can propogate events up to the main form from the ErrorCriteria and ErrorResults forms that are now bound to the subform controls. That will help me to use the basic MVC design pattern for VBA described here. I can treat the main form as the view, even though parts of that view are buried in subform controls. The controller only has to know about that one view.
My problem comes when the user selects a new "screen" from the dropdown menu. I think it would be nice to just re-purpose the subform controls, like so:
SubForm1.SourceObject = "Form.WarehouseCriteria"
SubForm2.SourceObject = "Form.InventoryResults"
And then just move/resize those subforms to the appropriate layout for the "Inventory" screen.
This approach seems to make the user interface design cleaner in my mind because you basically only ever have to deal with one main form that acts as a template and then you plug in the values (the SourceObject properties) into that template.
But each time we change the "screen", we have a totally different "Model" behind the scenes and a new "View" too according to the MVC design pattern. I wonder if that would clutter up the MVC VBA code behind the scenes, or if the VBA code itself could be modularized too (possibly using Interfaces) to make it just as adaptable as the user interface.
What is the cleanest way to do this from both a User Interface perspective, and from a VBA perspective. Use one main form as template where other forms could be swapped in and out as subforms, or just close the current form and open a new form when the user selects a new "screen" from the dropdown menu.
Below is a brief description of one way to 'repurpose' or reformat a form for several uses. Re your question of changing the VBA code, a simple solution would be to check a label value or some value you set in the control, then call the appropriate VBA subroutine.
We had over 100 reports available, each with their own selection criteria/options and we did not want to create a unique filter form for every report. The solution was to identify the selection options available by report, identify the logical order of those options, then create a table that would present the options to the user.
First, we created the table: ctlReportOptions (PK = ID, ReportName, OptionOrder)
Fields: ID (Int), ReportName (text), OptionOrder (Int), ControlName (text), ControlTop (Int), ControlLeft (Int), SkipLabel (Y/N), ControlRecordsourc(text)
Note 1: ID is not an AutoNumber.
Next we populated with records that would define the view the user would see.
Note 2: Using an ID of zero, we created records for EVERY field on the report so we could always redraw for the developers.
Then we created the form and placed controls for every possible filter.
We set the 'Default Value' property to be used as our default.
Some of the controls:
ComboBox to select the report name. Add code for Change event as follows:
Private Sub cboChooseReport_Change()
Dim strSQL As String
Dim rs As ADODB.recordSet
Dim i As Integer
Dim iTop As Integer
Dim iLeft As Integer
Dim iLblTop As Integer
Dim iLblLeft As Integer
Dim iLblWidth As Integer
Dim iTab As Integer
Dim strLabel As String
On Error GoTo Error_Trap
' Select only optional controls (ID <> 0); skip cotrols always present.
strSQL = "SELECT ctlRptOpt.ControlName, 'lbl' & Mid([ControlName],4,99) AS LabelName, SkipLabel " & _
"From ctlRptOpt WHERE (((ctlRptOpt.ID)<>0)) " & _
"GROUP BY ctlRptOpt.ControlName, 'lbl' & Mid([ControlName],4,99), SkipLabel;"
Set rs = New ADODB.recordSet
rs.Open strSQL, CurrentProject.Connection, adOpenDynamic
Do While Not rs.EOF
Me(rs!ControlName).Visible = False ' Hide control
If rs!skiplabel = False Then ' Hide Label if necessary
Me(rs!LabelName).Visible = False
End If
rs.MoveNext
Loop
rs.Close
iTop = 0
iTab = 0
' Get list of controls used by this report; order by desired sequence.
strSQL = "select * from ctlRptOpt " & _
"where [ID] = " & Me.cboChooseReport.Column(3) & _
" order by OptionOrder;"
Set rs = New ADODB.recordSet
rs.Open strSQL, CurrentProject.Connection, adOpenDynamic
If rs.EOF Then ' No options needed
Me.cmdShowQuery.Visible = True
Me.lblReportCriteria.Visible = False
Me.cmdShowQuery.left = 2000
Me.cmdShowQuery.top = 1500
Me.cmdShowQuery.TabIndex = 1
Me.cmdReset.Visible = False
rs.Close
Set rs = Nothing
GoTo Proc_Exit ' Exit
End If
' Setup the display of controls.
Me.lblReportCriteria.Visible = True
Do While Not rs.EOF
If rs!skiplabel = False Then
strLabel = "lbl" & Mid(rs!ControlName, 4)
iLblWidth = Me.Controls(strLabel).Width
Me(strLabel).top = rs!ControlTop
Me(strLabel).left = rs!ControlLeft - (Me(strLabel).Width + 50)
Me(strLabel).Visible = True
End If
iTab = iTab + 1 ' Set new Tab Order for the controls
Me(rs!ControlName).top = rs!ControlTop
Me(rs!ControlName).left = rs!ControlLeft
Me(rs!ControlName).Visible = True
If left(rs!ControlName, 3) <> "lbl" Then
Me(rs!ControlName).TabIndex = iTab
End If
If Me(rs!ControlName).top >= iTop Then
iTop = rs!ControlTop + Me(rs!ControlName).Height ' Save last one
End If
' If not a label and not a 'cmd', it's a filter! Set a default.
If left(rs!ControlName, 3) <> "lbl" And left(rs!ControlName, 3) <> "cmd" Then
If Me(rs!ControlName).DefaultValue = "=""*""" Then
' Me(rs!ControlName) = "*"
ElseIf left(Me(rs!ControlName).DefaultValue, 2) = "=#" And right(Me(rs!ControlName).DefaultValue, 1) = "#" Then
i = Len(Me(rs!ControlName).DefaultValue)
' Me(rs!ControlName) = Mid(Me(rs!ControlName).DefaultValue, 3, i - 3)
ElseIf Me(rs!ControlName).DefaultValue = "True" Then
' Me(rs!ControlName) = True
ElseIf Me(rs!ControlName).DefaultValue = "False" Then
' Me(rs!ControlName) = False
End If
Else
If Me(rs!ControlName).top + Me(rs!ControlName).Height >= iTop Then
iTop = rs!ControlTop + Me(rs!ControlName).Height ' Save last one
End If
End If
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
If Me.cboChooseReport.Column(1) <> "rptInventoryByDate" Then ' It's special
Me.cmdShowQuery.Visible = True
Me.cmdShowQuery.left = 2000
Me.cmdShowQuery.top = iTop + 300
iTab = iTab + 1
Me.cmdShowQuery.TabIndex = iTab
Else
Me.cmdShowQuery.Visible = False
End If
Me.cmdReset.Visible = True
Me.cmdReset.left = 5000
Me.cmdReset.top = iTop + 300
Me.cmdReset.TabIndex = iTab + 1
Proc_Exit:
Exit Sub
Error_Trap:
Err.Source = "Form_frmReportChooser: cboChooseReport_Change at Line: " & Erl
DocAndShowError ' Save error to database for analysis, then display to user.
Resume Proc_Exit ' Exit code.
Resume Next ' All resumption if debugging.
Resume
End Sub
lblReportCriteria: We displayed the final set of filters so when users complained of nothing showing on the report, we asked them to send us a screen print. We also passed this text to the report and it was printed as a footer on the last page.
cmdReset: Reset all controls back to their default values.
cmdShowQuery: Executes the running of the report
Private Sub cmdShowQuery_Click()
Dim qdfDelReport101 As ADODB.Command
Dim qdfAppReport101 As ADODB.Command
Dim qdfDelReport102 As ADODB.Command
Dim qdfAppReport102 As ADODB.Command
Dim qryBase As ADODB.Command
Dim strQueryName As String
Dim strAny_Open_Reports As String
Dim strOpen_Report As String
Dim qdfVendorsInfo As ADODB.Command
Dim rsVendorName As ADODB.recordSet
Dim strVendorName As String
Dim rsrpqFormVendorsInfo As ADODB.recordSet
On Error GoTo Error_Trap
If Not IsNull(Me.cboChooseReport.value) And Me.cboChooseReport.value <> " " Then
strAny_Open_Reports = Any_Open_Reports()
If Len(strAny_Open_Reports) = 0 Then
If Me.cboChooseReport.value = "rptAAA" Then
BuildReportCriteria '
If Me.chkBankBal = True Then
DoCmd.OpenReport "rptAAA_Opt1", acViewPreview
Else
DoCmd.OpenReport "rptAAA_Opt2", acViewPreview
End If
ElseIf Me.cboChooseReport.value = "rptBBB" Then
If IsNull(Me.txtFromDate) Or Not IsDate(Me.txtFromDate) Then
MsgBox "You must enter a valid From Date", vbOKOnly, "Invalid Date"
Exit Sub
End If
If IsNull(Me.txtToDate) Or Not IsDate(Me.txtToDate) Then
MsgBox "You must enter a valid To Date", vbOKOnly, "Invalid Date"
Exit Sub
End If
Me.txtStartDate = Me.txtFromDate
Me.txtEndDate = Me.txtToDate
DoCmd.OpenReport Me.cboChooseReport.value, acViewPreview
ElseIf Me.cboChooseReport.value = "rptCCC" Then
If Me.txtVendorName = "*" Then
gvstr_VendorName = "*"
Else
Set rsVendorName = New ADODB.recordSet
rsVendorName.Open "selVendorName", gv_DBS_Local, adOpenDynamic
Set qdfVendorsInfo = New ADODB.Command
qdfVendorsInfo.ActiveConnection = gv_DBS_SQLServer
qdfVendorsInfo.CommandText = ("qryVendorsInfo")
qdfVendorsInfo.CommandType = adCmdStoredProc
strVendorName = rsVendorName("VendorName")
gvstr_VendorName = strVendorName
End If
DoCmd.OpenReport "rptFormVendorReport", acViewPreview
Else
BuildReportCriteria
If Me.cboChooseReport.value = "rptXXXXXX" Then
ElseIf Me.cboChooseReport.value = "rptyyyy" Then
On Error Resume Next ' All resumption if debugging.
DoCmd.DeleteObject acTable, "temp_xxxx"
On Error GoTo Error_Trap
Set qryBase = New ADODB.Command
qryBase.ActiveConnection = gv_DBS_Local
qryBase.CommandText = ("mtseldata...")
qryBase.CommandType = adCmdStoredProc
qryBase.Execute
End If
DoCmd.Hourglass False
DoCmd.OpenReport Me.cboChooseReport.value, acViewPreview
End If
Else
MsgBox "You cannot open this form/report because you already have a form/report(s) open: " & _
vbCrLf & strAny_Open_Reports & _
vbCrLf & "Please close the open form/report(s) before continuing."
strOpen_Report = Open_Report
DoCmd.SelectObject acReport, strOpen_Report
DoCmd.ShowToolbar "tbForPost"
End If
Else
MsgBox "Please Choose Report", vbExclamation, "Choose Report"
End If
Exit Sub
Error_Trap:
Err.Source = "Form_frmReportChooser: cmdShowQuery_Click - Report: " & Nz(Me.cboChooseReport.value) & " at Line: " & Erl
If Err.Number = 2501 Then ' MsgBox "You chose not to open this report.", vbOKOnly, "Report cancelled"
Exit Sub
ElseIf Err.Number = 0 Or Err.Number = 7874 Then
Resume Next ' All resumption if debugging.
ElseIf Err.Number = 3146 Then ' ODBC -- call failed -- can have multiple errors
Dim errLoop As Error
Dim strError As String
Dim Errs1 As Errors
' Enumerate Errors collection and display properties of each Error object.
i = 1
Set Errs1 = gv_DBS_SQLServer.Errors
Err.Description = Err.Description & "; Err.Count = " & gv_DBS_SQLServer.Errors.Count & "; "
For Each errLoop In Errs1
With errLoop
Err.Description = Err.Description & "Error #" & i & ":" & " ADO Error#" & .Number & _
" Description= " & .Description
i = i + 1
End With
Next
End If
DocAndShowError ' Save error to database for analysis, then display to user.
Exit Sub
Resume Next ' All resumption if debugging.
Resume
End Sub
Function to build a string showing all of the selection criteria:
Function BuildReportCriteria()
Dim frmMe As Form
Dim ctlEach As Control
Dim strCriteria As String
Dim prp As Property
Dim strSQL As String
Dim rs As ADODB.recordSet
On Error GoTo Error_Trap
strSQL = "select * from ctlRptOpt " & _
"where ID = " & Me.cboChooseReport.Column(3) & _
" order by OptionOrder;"
Set rs = New ADODB.recordSet
rs.Open strSQL, CurrentProject.Connection, adOpenDynamic
If rs.EOF Then
strCriteria = " Report Criteria: None"
Else
strCriteria = " Report Criteria: "
End If
Do While Not rs.EOF
Set ctlEach = Me.Controls(rs!ControlName)
If ctlEach.ControlType = acTextBox Or ctlEach.ControlType = acComboBox Then
If ctlEach.value <> "*" And ctlEach.Name <> "cboChooseReport" And ctlEach.Name <> "cboLocCountry" Then
strCriteria = strCriteria & ctlEach.Tag & " = " & ctlEach.value & " , "
End If
End If
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
If Me.chkOblBal = -1 Then
strCriteria = strCriteria & "Non-zero balances only = Yes"
Else
'return string with all choosen criteria and remove last " , " from the end of string
strCriteria = left$(strCriteria, Len(strCriteria) - 3)
End If
fvstr_ReportCriteria = strCriteria
Set ctlEach = Nothing
Exit Function
Error_Trap:
If Err.Number = 2447 Then
Resume Next ' All resumption if debugging.
End If
Err.Source = "Form_frmReportChooser: BuildReportCriteria at Line: " & Erl
DocAndShowError ' Save error to database for analysis, then display to user.
Exit Function
Resume Next ' All resumption if debugging.
End Function
Finally, each report had it's own query that would filter based on the values in the controls on this form.
Hope this helps. If you are curious about any of the weird things you see, let me know. (i.e. we always used line numbers in the code (I deleted before posting) that allowed us to identify exact line where code fails)
Using Access 2010, I have a form for Purchase_Orders where the status changes depending on the whether the Items in the sub form have been delivered or not, and, it is influenced by the date as well.
Private Sub Form_AfterUpdate()
Dim rs As Recordset
Dim db As Database
Dim var_Delivered As String
var_Delivered = "SELECT Count(*) AS d_Count" & _
" FROM Items" & _
" WHERE PO_ID =" & Me.PO_ID.Value & _
" AND Supplier_Dnote_ID IS Null" & _
" AND Delivered_Without_Dnote =0;"
Set db = CurrentDb
Set rs = db.OpenRecordset(var_Delivered, dbOpenDynaset)
'MsgBox rs!d_Count
If rs!d_Count > 0 Then
If Me.Supply_date < Date Then
Me.Status = "Overdue"
Else
Me.Status = "Submitted"
End If
Else
Me.Status = "Delivered"
End If
db.Close
Set db = Nothing
Set rs = Nothing
End Sub
This runs after_update of the Purchase_Orders. I have a save_close button that uses the following code and doesn't return an error:
If Me.Dirty = True Then
DoCmd.Close acForm, "Purchase_Orders", acSaveYes
Else
DoCmd.Close acForm, "Purchase_Orders", acSaveNo
End If
However, I also have a Save button that doesn't close the form. This is where I get run-time error 2759 : The method you tried to invoke on an object failed. Debug Highlights the saverecord line.
Private Sub SaveOnlyBtn_Click()
If Me.Dirty = True Then
docmd.RunCommand acCmdSaveRecord
End If
End Sub
If I comment the status code out and use the save button, the record saves fine without any errors. Why do I get this error? I'm completely stumped and searching the error online hasn't helped me either.
So I found that the error did not occur when I put the code in the "on dirty" event, which then made me realise that I don't need necessarily have to run the code after the form updates, only when specific fields change. So I changed my code to a public code and called it when supply date, delivered_without_dnote, or supplier_Invoice_ID changed.
the public code is :
Public Sub delivered_status()
On Error GoTo errTrap1
If Forms!Purchase_Orders_Ex.Form!Status = "Cancelled" Then
Exit Sub
Else
DoCmd.RunCommand acCmdSaveRecord
Dim rs As Recordset
Dim db As Database
Dim var_Delivered As String
var_Delivered = "SELECT Count(*) AS d_Count" & _
" FROM Items" & _
" WHERE PO_ID =" & Forms!Purchase_Orders_Ex.Form!PO_ID.Value & _
" AND Supplier_Dnote_ID IS Null" & _
" AND Delivered_Without_Dnote =0;"
Set db = CurrentDb
Set rs = db.OpenRecordset(var_Delivered, dbOpenDynaset)
'MsgBox "Outstanding Items: " & rs!d_Count
If rs!d_Count > 0 Then
If Forms!Purchase_Orders_Ex.Form!Supply_date < Date Then
Forms!Purchase_Orders_Ex.Form!Status = "Overdue"
Else
Forms!Purchase_Orders_Ex.Form!Status = "Submitted"
End If
Else
Forms!Purchase_Orders_Ex.Form!Status = "Delivered"
End If
rs.Close
Set db = Nothing
Set rs = Nothing
End If
errTrap1:
Select Case Err.Number
Case 3314 'form not complete and other required fields are empty
Exit Sub
Case Else
If Err.Number > 0 Then
MsgBox Err.Number & ": " & Err.Description
End If
End Select
End Sub
Now, when I use either the save_close or Save_Only I do not get error 2759. I do not completely understand which part of my original method caused the error but it no longer occurs with this approach.
I've just encountered this issue and moving code out of Form_AfterUpdate fixed it for me too.
What's (vaguely) interesting is that the code in question worked fine locally, but did not work when deployed to the client. I tried importing just the amended form instead of replacing the whole access app, but I still got the same issue. I also copied the back-end database back from the server to my development machine, but still didn't get the issue locally. On top of that I did endless compact/repair and decompile/compile.
My conclusion at the end of all of that was that this was yet another weird issue emanating from the Access black-box, rather than an issue with the particular code.
I have a subroutine in my errorhandling function that attempts to close every workbook open in every instance of Excel. Otherwise, it might stay in memory and break my next vbscript. It should also close every workbook without saving any changes.
Sub CloseAllExcel()
On Error Resume Next
Dim ObjXL As Excel.Application
Set ObjXL = GetObject(, "Excel.Application")
If Not (ObjXL Is Nothing) Then
Debug.Print "Closing XL"
ObjXL.Application.DisplayAlerts = False
ObjXL.Workbooks.Close
ObjXL.Quit
Set ObjXL = Nothing
Else
Debug.Print "XL not open"
End If
End Sub
This code isn't optimal, however. For example, it can close 2 workbooks in one instance of Excel, but if you open 2 instances of excel, it will only close out 1.
How can I rewrite this to close all Excel without saving any changes?
Extra Credit:
How to do this for Access as well without closing the Access file that is hosting this script?
You should be able to use window handles for this.
Public Sub CloseAllOtherAccess()
Dim objAccess As Object
Dim lngMyHandle As Long
Dim strMsg As String
On Error GoTo ErrorHandler
lngMyHandle = Application.hWndAccessApp
Set objAccess = GetObject(, "Access.Application")
Do While TypeName(objAccess) = "Application"
If objAccess.hWndAccessApp <> lngMyHandle Then
Debug.Print "found another Access instance: " & _
objAccess.hWndAccessApp
objAccess.Quit acQuitSaveNone
Else
Debug.Print "found myself"
Exit Do
End If
Set objAccess = GetObject(, "Access.Application")
Loop
ExitHere:
Set objAccess = Nothing
On Error GoTo 0
Exit Sub
ErrorHandler:
strMsg = "Error " & Err.Number & " (" & Err.Description _
& ") in procedure CloseAllOtherAccess"
MsgBox strMsg
GoTo ExitHere
End Sub
It appears to me GetObject returns the "oldest" Access instance. So that sub closes all Access instances started before the one which is running the sub. Once it finds itself, it stops. Maybe that's fine for your situation. But if you need to also close Access instances started after the one which is running the code, look to Windows API window handle functions.
I didn't try this approach for Excel. But I did see Excel provides Application.Hwnd and Application.Hinstance ... so I suspect you can do something similar there.
Also, notice I got rid of On Error Resume Next. GetObject will always return an Application object in this sub, so it didn't serve any purpose. Additionally, I try to avoid On Error Resume Next in general.
Update: Since GetObject won't do the job for you, use a different method to get the window handles of all the Access instances. Close each of them whose window handle doesn't match the one you want to leave running (Application.hWndAccessApp).
Public Sub CloseAllAccessExceptMe()
'FindWindowLike from: '
'How To Get a Window Handle Without Specifying an Exact Title '
'http://support.microsoft.com/kb/147659 '
'ProcessTerminate from: '
'Kill a Process through VB by its PID '
'http://en.allexperts.com/q/Visual-Basic-1048/Kill-Process-VB-its-1.htm '
Dim lngMyHandle As Long
Dim i As Long
Dim hWnds() As Long
lngMyHandle = Application.hWndAccessApp
' get array of window handles for all Access top level windows '
FindWindowLike hWnds(), 0, "*", "OMain", Null
For i = 1 To UBound(hWnds())
If hWnds(i) = lngMyHandle Then
Debug.Print hWnds(i) & " -> leave myself running"
Else
Debug.Print hWnds(i) & " -> close this one"
ProcessTerminate , hWnds(i)
End If
Next i
End Sub
Differentiating open instances of an application is a very old problem, and it is not unique to VBA.
I've tried to figure this out myself over the years, never with greater success than the time before.
I think the long and short of it is that you can never know if the application instance you're referencing is the one in which the code is executing (so terminating it might leave other instances open).
I just tried the following with both Excel and Access :
Dim sKill As String
sKill = "TASKKILL /F /IM msaccess.exe"
Shell sKill, vbHide
If you change the msaccess.exe to excel.exe, excel will be killed.
If you want a bit more control over the process, check out:
http://www.vbaexpress.com/kb/getarticle.php?kb_id=811
I know this is an old post but for those who visit here from searches may find it helpful.
This code was found and modified. It will give you every SHEET in every WORKBOOK in every INSTANCE. From there you can determine the active instance.
Module..............
Declare Function FindWindowEx Lib "User32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Declare Function GetClassName Lib "User32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Declare Function IIDFromString Lib "ole32" (ByVal lpsz As Long, ByRef lpiid As UUID) As Long
Declare Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hWnd As Long, ByVal dwId As Long, ByRef riid As UUID, ByRef ppvObject As Object) As Long
Type UUID 'GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Code…………………...
Const IID_IDispatch As String = "{00020400-0000-0000-C000-000000000046}"
Const OBJID_NATIVEOM As Long = &HFFFFFFF0
Sub ListAll()
Dim I As Integer
Dim hWndMain As Long
On Error GoTo MyErrorHandler
hWndMain = FindWindowEx(0&, 0&, "XLMAIN", vbNullString)
I = 1
Do While hWndMain <> 0
Debug.Print "Excel Instance " & I
GetWbkWindows hWndMain
hWndMain = FindWindowEx(0&, hWndMain, "XLMAIN", vbNullString)
I = I + 1
Loop
Exit Sub
MyErrorHandler:
MsgBox "GetAllWorkbookWindowNames" & vbCrLf & vbCrLf & "Err = " & Err.Number & vbCrLf & "Description: " & Err.Description
End Sub
Sub GetWbkWindows(ByVal hWndMain As Long)
Dim hWndDesk As Long
Dim hWnd As Long
Dim strText As String
Dim lngRet As Long
On Error GoTo MyErrorHandler
hWndDesk = FindWindowEx(hWndMain, 0&, "XLDESK", vbNullString)
If hWndDesk <> 0 Then
hWnd = FindWindowEx(hWndDesk, 0, vbNullString, vbNullString)
Do While hWnd <> 0
strText = String$(100, Chr$(0))
lngRet = GetClassName(hWnd, strText, 100)
If Left$(strText, lngRet) = "EXCEL7" Then
GetExcelObjectFromHwnd hWnd
Exit Sub
End If
hWnd = FindWindowEx(hWndDesk, hWnd, vbNullString, vbNullString)
Loop
On Error Resume Next
End If
Exit Sub
MyErrorHandler:
MsgBox "GetWbkWindows" & vbCrLf & vbCrLf & "Err = " & Err.Number & vbCrLf & "Description: " & Err.Description
End Sub
Function GetExcelObjectFromHwnd(ByVal hWnd As Long) As Boolean
Dim fOk As Boolean
Dim I As Integer
Dim obj As Object
Dim iid As UUID
Dim objApp As Excel.Application
Dim myWorksheet As Worksheet
On Error GoTo MyErrorHandler
fOk = False
Call IIDFromString(StrPtr(IID_IDispatch), iid)
If AccessibleObjectFromWindow(hWnd, OBJID_NATIVEOM, iid, obj) = 0 Then 'S_OK
Set objApp = obj.Application
For I = 1 To objApp.Workbooks.Count
Debug.Print " " & objApp.Workbooks(I).Name
For Each myWorksheet In objApp.Workbooks(I).Worksheets
Debug.Print " " & myWorksheet.Name
DoEvents
Next
fOk = True
Next I
End If
GetExcelObjectFromHwnd = fOk
Exit Function
MyErrorHandler:
MsgBox "GetExcelObjectFromHwnd" & vbCrLf & vbCrLf & "Err = " & Err.Number & vbCrLf & "Description: " & Err.Description
End Function
I hope this helps someone :)
This is a response to an old post, but same as the poster in 2012, hopefully it helps someone who may come here based on a generic web search.
Background
My company uses XLSX "models" to turn our data into "pretty" automatically. The data exports from SAS as XLS; we do not have the licensing or add-ons to export as XLSX. The normal process is to copy/paste each of the 14 SAS outputs into the XLSX. The code below iterates through the first two exports where data is copied from the XLS, pasted into the XLSX, and the XLS closed.
Please note: The XLSX file is saved to the hard drive. The XLS files are NOT SAVED, i.e. the path goes to "My Documents/" but there is no file name or file visible there.
Sub Get_data_from_XLS_to_XLSX ()
Dim xlApp1 As Excel.Application
Dim xlApp2 As Excel.Application
'Speed up processing by turning off Automatic Calculations and Screen Updating
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
'Copies data from Book1 (xls) and pastes into ThisWorkbook (xlsx), then closes xls file
Set xlApp1 = GetObject("Book1").Application
xlApp1.Workbooks("Book1").Sheets("Sheet1").Range("A2:E2").Copy
Application.ThisWorkbook.Worksheets("Data1").Cells(5, 2).PasteSpecialPaste:=xlPasteValues
'Clears the clipboard, turns off the "do you want to save" alert, closes xls file
xlApp1.CutCopyMode = False
xlApp1.DisplayAlerts = False
xlApp1.Quit
xlApp1.DisplayAlerts = True
'Same as the first one above, but now it's a second/different xls file, i.e. Book2
Set xlApp2 = GetObject("Book2").Application
xlApp2.Workbooks("Book2").Sheets("Sheet1").Range("A2:E2").Copy
Application.ThisWorkbook.Sheets("Data2").Cells(10, 2).PasteSpecial Paste:=xlPasteValues
'Clears the clipboard, turns off the "do you want to save" alert, closes xls file
xlApp2.CutCopyMode = False
xlApp2.DisplayAlerts = False
xlApp2.Quit
xlApp2.DisplayAlerts = True
'Sub continues for 12 more iterations of similar code
End Sub
You need to be explicit in qualifying your statements. i.e. instead of Workbooks("Book_Name") make sure you identify the application you are referring to, be it Application.Workbooks("Book_Name") or xlApp1.Workbooks("Book_Name")
try putting it in a loop
Set ObjXL = GetObject(, "Excel.Application")
do until ObjXL Is Nothing
Debug.Print "Closing XL"
ObjXL.Application.DisplayAlerts = False
ObjXL.Workbooks.Close
ObjXL.Quit
Set ObjXL = Nothing
Set ObjXL = GetObject(, "Excel.Application") ' important!
loop