I have a form with a text box named Contract_Applying_for which is disabled on form load, but I want to have a button which allows me to edit the contents of the text box.
When I add a button I get presented with the Command Button Wizard, so I have created a Macro called ToggleEnableButton which has the instruction to
RunCode Function Name "=ToggleEnableButton()"
Then I have written the function
Function ToggleEnableButton()
If Me.Contract_Applying_for.Enabled = True Then
Me.Contract_Applying_for.Enabled = False
Else
Me.Contract_Applying_for.Enabled = True
End If
End Function
This seems to produce the error "Member already exists in an object module from which this object module derives."
The code for the ToggleEnableButton_Click is automatically created by the Command Button Wizard and is
Private Sub ToggleEnableButton_Click()
On Error GoTo Err_ToggleEnableButton_Click
Dim stDocName As String
stDocName = "ToggleEnableMacro"
DoCmd.RunMacro stDocName
Exit_ToggleEnableButton_Click:
Exit Sub
Err_ToggleEnableButton_Click:
MsgBox Err.Description
Resume Exit_ToggleEnableButton_Click
End Sub
Any suggestion of what I am doing wrong or a better way to approach this.
Seems like a very simple thing that I am trying to do, but quite a long winded approach.
As suggested by Peekay in the comments I have tried to use a checkbox instead, I wrote
Private Sub chbToggleEdit_Click()
If Me.chbToggleEdit.Value = False Then
Me.Contract_Applying_for.Enabled = False
Else
Me.Contract_Applying_for.Enabled = True
End If
End Sub
This gives the error: "A problem occurred while Microsoft Access was communication with the OLE server or ActiveX Control."
Can you not simply have:
Private Sub ToggleEnableButton_Click()
On Error GoTo Err_ToggleEnableButton_Click
ToggleEnableButton()
Exit_ToggleEnableButton_Click:
Exit Sub
Err_ToggleEnableButton_Click:
MsgBox Err.Description
Resume Exit_ToggleEnableButton_Click
End Sub
You can declare your function with the code behind your form.
Let me know if that works,
Ash
Related
I am trying create a button which changes the value of AllowEdits to False and another for true for a subform. I am using the below code. I get a Runtime error 424 each time I run it.
Option Compare Database
Private Sub Toggle_Edit_Click()
Dim strForm As String
strFormName = Me.Name
Call ToggleEdit(Me)
End Sub
and
Option Compare Database
Public strFormName As String
Sub ToggleEdit(myForm As Form)
Call Message
ctrlControl.AllowEdits = True
End Sub
and if you were interested
Sub Message()
MsgBox "Remember not to overwrite incorrect records"
End Sub
Please add Option Explicit at top of your modules!
I think AllowEdits is a Form property, not a Control property.
Option Explicit
Sub ToggleEdit(myForm As Form)
myForm.AllowEdits = Not myForm.AllowEdits
End Sub
If the code is behind the form itself, you can use Me.
Sub ToggleEdit() 'no parameter
Me.AllowEdits = Not Me.AllowEdits
End Sub
If you want to act at control level, use Locked or Enabled properties.
I have a function that has been in use for a number of months that checks to see if the form that is going to be opened will actually have records to be viewed before opening it. Recently I decided to change from ACCDB to ACCDE for security purposes. After making the change over the function started throwing error 2450 "Microsoft Access cannot find the referenced form..." I can't seem to find anything of use online that could tell me what the cause of this error is and why it only happens with ACCDE.
On a side note I realize the inefficiency of the logic in this function, it's on my list.
Public Function ValidateFormToOpen(strFormName As String, strFilter As String, strFieldName As String) As Boolean
On Error GoTo Err_Handler
Dim intNumberOfRecords As Integer
'If the form is currently open count how many results will be shown
If CheckFormState(strFormName) Then
intNumberOfRecords = DCount(strFieldName, Access.Forms(strFormName).RecordSource, strFilter)
'If it is closed open it in a hidden state and then count how many records would be shown
Else
DoCmd.OpenForm strFormName, acDesign, "", strFilter, , acHidden
intNumberOfRecords = DCount(strFieldName, Access.Forms(strFormName).RecordSource, strFilter)
DoCmd.Close acForm, strFormName
End If
'If there were records that will be shown return true
If intNumberOfRecords > 0 Then
ValidateFormToOpen = True
Else
ValidateFormToOpen = False
End If
Exit_Handler:
Exit Function
Err_Handler:
Call LogError(Err.Number, Err.Description, strMODULE_NAME & ".ValidateFormToOpen on " & strFormName)
Resume Exit_Handler
End Function
This is the CheckFormState Code
Public Function CheckFormState(sFormName As String) As Boolean
On Error GoTo Err_Handler
If Access.Forms(sFormName).Visible = True Then
CheckFormState = True
End If
Exit_Handler:
Exit Function
Err_Handler:
CheckFormState = False
Resume Exit_Handler
End Function
An ACCDE format database restricts design capabilities in general. I think that may be why you get an error with this line:
DoCmd.OpenForm strFormName, acDesign, "", strFilter, , acHidden
However I'm not positive that is the complete explanation because when I attempt to open a form in Design View (DoCmd.OpenForm "Form1", acDesign) in my ACCDE database, Access gives me a different error message:
"The command you specified is not available in an .mde, .accde, or .ade database."
So I don't know what the solution is for your goal, but I believe it can not be based on opening a form in Design View.
I am using David-W-Fenton's answer to this question to try to allow users to print a report when they click on a control, but I am getting the following error message:
runtime error 2448: you cannot assign a value to this object.
The line of code triggering the error is:
Me.txtPageTo.Value = numPages
Here is the full code of the OnLoad event handler for the form:
Private Sub Form_Load()
Dim varPrinter As Printer
Dim strRowsource As String
Dim strReport As String
If Len(Me.OpenArgs) > 0 Then
strReport = Me.OpenArgs
Me.Tag = strReport
For Each varPrinter In Application.Printers
strRowsource = strRowsource & "; " & varPrinter.DeviceName
Next varPrinter
Me!cmbPrinter.RowSource = Mid(strRowsource, 3)
' first check to see that the report is still open
If (1 = SysCmd(acSysCmdGetObjectState, acReport, strReport)) Then
With Reports(strReport).Printer
Me!cmbPrinter = .DeviceName
Me!optLayout = .Orientation
End With
Dim numPages As String
numPages = Reports(strReport).Pages
Debug.Print "numPages = " & numPages
TypeName(Me.txtPageTo) 'added this line as a test after
Me.txtPageTo.Value = numPages
End If
End If
End Sub
numPages prints out to equal 0 just before the error is thrown, even though the report should have at least one page. Also, the error does not get thrown when the form is already open. The error only gets thrown when the form has to be opened. (Probably because the offending code is in the onload event.)
When I added TypeName(Me.txtPageTo) , it triggered runtime error 2424: The expression you entered has a field, control, or property name that mydatabasename cant find.
I think the problem is that I need to set the control source for txtPageFrom and txtPageTo. But how do I do that? This form will only be loaded by a vba method that is trying to print a report, so the values for txtPageFrom and txtPageTo will come from the calling method, not from some underlying data table. (there is no underlying data table for this dialog form.)
cmbPrinter and optLayout seem to be populating correctly using the code above.
Can anyone show me how to get past the error messages so that the form loads properly?
CORRECT, COMPLETE ANSWER:
The answer to this problem was to greatly simplify the code. The code in the link at the top of this posting is WAY TOO COMPLEX, and tries to reinvent things that are already done well by the tools built into Access and VBA. I was able to print reports without any of the complicated solutions by simply using the following code in the on-click event of a control on a form associated with the report:
Private Sub txtPrintReport_Click()
On Error GoTo Error_Handler
Dim commNum As Long
commNum = Me.CommunicationNumber
Dim strReport As String
Dim strVarName As String
strReport = "rptCommunicationFormForPrinting"
strVarName = "CommunicationNumber"
DoCmd.OpenReport strReport, acViewPreview, , strVarName & " = " & commNum, acWindowNormal, acHidden
DoCmd.RunCommand acCmdPrint
DoCmd.Close acReport, strReport
Exit_Point:
Exit Sub
Error_Handler: 'this handles the case where user clicks cancel button
If Err.Number <> 2501 Then
MsgBox Err.Description, _
vbExclamation, "Error " & Err.Number
End If
DoCmd.Close acReport, strReport
End Sub
That is all the code that was required. Much less than the link at the start of this question. And also much less than the answer below. I am marking John Bingham's answer as the accepted answer because he clearly worked a lot on this, and I am very grateful for that. But I ended up solving the problem with A LOT LESS CODE. My solution does not require the custom dialog form because it uses the Windows print dialog form. As such, the on load event code in my posting (taken from the link above), is not necessary.
The code posted cannot work, because when a form is opened as a dialog:
DoCmd.OpenForm "dlgPrinter", , , , , acDialog, strReport
no other logic after this line is executed until the form has been closed, at which point control returns to the next line after this one, and logic continues on - except of course you can no longer reference anything in that form, because it is now closed.
Ok, now there is a question, where is the button the user clicks to print a report?
What I'm thinking is to split the logic in PrintReport into two methods, one that launches it, and tells the form to configure itself (doing the stuff suggested in the comment), but the rest of PrintReport then needs to happen after the user clicks OK (or Cancel);
So if I assume that you've got a form which can launch one or more reports, and the button is on this form, what I would suggest is this:
In the click event for that button - no changes to what you've got.
In that buttons form, add this:
Public Sub DialogAccept()
With Forms!dlgPrinter
If .Tag <> "Cancel" Then
Set Reports(strReport).Printer = Application.Printers((!cmbPrinter))
Reports(strReport).Printer.Orientation = !optLayout
Application.Echo False
DoCmd.SelectObject acReport, strReport
DoCmd.PrintOut acPages, !txtPageFrom, !txtPageTo
PrintReport = True
End If
End With
DoCmd.Close acForm, "dlgPrinter"
DoCmd.Close acReport, strReport
Application.Echo True
End Sub
Change PrintReport to:
Public Function PrintReport(strReport As String, strVarName As String, numVal As Long) As Boolean
' open report in PREVIEW mode but HIDDEN
DoCmd.OpenReport strReport, acViewPreview, , strVarName & " = " & numVal, acHidden
'DoCmd.OpenReport strReport, acViewPreview, , , acHidden
' open the dialog form to let the user choose printing options
DoCmd.OpenForm "dlgPrinter", , , , , , strReport
Forms!dlgPrinter.Configure
End Function
In the OK/Cancel button click events on dlgPrinter put (after existing code, but removing any instances of "Docmd.close"):
Forms!Calling_Form_Name.DialogAccept
This then calls that method, to do the stuff that is meant to happen after the user says "I'm done with the dialog".
Finally add the configure method to dlgPrinter:
Public Sub Configure()
With Reports(Me.Tag).Printer
Me!cmbPrinter = .DeviceName
Me!optLayout = .Orientation
End With
Dim numPages As String
numPages = Reports(Me.Tag).Pages
Debug.Print "numPages = " & numPages
TypeName(Me.txtPageTo) 'added this line as a test after
Me.txtPageTo.Value = numPages
End Sub
And remove this code section from Form_Load.
Hoep this helps.
Lastly, if the form on which the button launching dlgPrinter can vary, change this line:
DoCmd.OpenForm "dlgPrinter", , , , , acDialog, strReport
to:
DoCmd.OpenForm "dlgPrinter", , , , , acDialog, strReport & ";" & me.Name
In form_load of dlgPrinter, break up me.Openargs using left() & mid() with instr(), and save me.Name into the tag of something on the form (which you're not already using the tag of).
In the OK/Cancel button click events, change the code above to:
Forms(Object.Tag).DialogAccept
I'm having an issue with report events that I haven't
encountered before in Access prior to Access 2007.
I'm using using Access 2007 for a Front-end to a SQL Back-end.
I have a Class, ReportEvents, that I use for the reports.
In a report's Report_Open event I instantiate and then use this class to
handle events like activate, Close, NoData and I also put common code
such as exporting data to excel instead of a report.
This code was working fine in a previous Access 2003 application (mdb) I was using,
but it isn't working as expected in 2007 (accdb). In my tests the call to a non event public sub, ProvideXLOption works like a charm, but none of the events are being fired
from the ReportEvents class. I didn't change the code I just imported it into the
project. I set up break points but they aren't being hit. I changed all of them to
public events and then called them within the test reports event and they worked fine.
I set up another report in Access 2007 with the same results. I've checked
the startup settings in Access and they are fine. I even removed and re-added
the database location from the trusted locations without any luck.
Has Microsoft modified the Events Code or is this just a simple code error on my part that I'm not seeing? It's gotta be something simple. My brain is just toast (my son decided to stay awake after last night's feeding).
Class ReportEvents Code:
Option Compare Database
Option Explicit
Private WithEvents rpt As Access.Report
Const strEventKey As String = "[Event Procedure]"
Public Property Set Report(Rept As Access.Report)
Set rpt = Rept
With rpt
.OnActivate = strEventKey
.OnClose = strEventKey
If LenB(.OnNoData) = 0 Then
.OnNoData = strEventKey
End If
End With
End Property
Public Sub Terminate()
On Error Resume Next
Set rpt = Nothing
End Sub
Private Sub rpt_Activate()
LoadPrintRibbon
End Sub
Private Sub rpt_Close()
Me.Terminate
End Sub
Private Sub rpt_NoData(Cancel As Integer)
Dim strMsg As String
strMsg = "No Records were found that match your criteria."
MsgBox strMsg, vbInformation, rpt.Name & _
": No Match. Report Cancelled"
Cancel = True
End Sub
Private Sub LoadPrintRibbon()
#If AccessVersion >= 12 Then
If rpt.RibbonName <> "PrintPreview" Then
rpt.RibbonName = "PrintPreview"
End If
#End If
End Sub
';;Provides user with option to send data to Excel instead of a report
Public Sub ProvideXLOption(Cancel As Integer)
'... some XLOption Code
End Sub
In the Test Report Code:
Private Sub Report_Open(Cancel As Integer)
Dim rptEvts As ReportEvents
Set rptEvts = New ReportEvents
Set rptEvts.Report = Me
';;Let User User Choose to Export Data to Excel or Display the report
rptEvts.ProvideXLOption Cancel
End Sub
I figured it out. It was a scope issue The ReportEvents class variable rptEvts, was inside the Report_Open sub. Because of this it wouldn't exist when the other events happened. It should be at the module level and not within the procedure.
Dim rptEvts As ReportEvents
Private Sub Report_Open(Cancel As Integer)
Set rptEvts = New ReportEvents
Set rptEvts.Report = Me
';;Let User User Choose to Export Data to Excel or Display the report
rptEvts.ProvideXLOption Cancel End Sub
End Sub
It's amazing what a little rest will do for you.
I am considering the use of a tab control on a parent form for which I would like to have around 20 tabs. Each tab I am considering the use of one or two separate sub forms. Each sub form will have varied complexity in coded logic. By taking this approach will I severally reduce the performance of my application? I am currently using this in MS Access 2003. I will expect an average of 15 users at any given time on the various forms.
Thoughts?
Yes, performance will be degraded slightly for each subform. One or three isn't too bad but twenty is definitely going to cause you performance issues.
Once you have the subform working to your satisfaction either save the Record Source as a query and give it a name or save the query SQL string. Then either paste the query name or the query SQL string in the VBA code in the tab control change event.
Private Sub TabCtl_Change()
On Error GoTo TabCtl_Change_Error
Select Case Me.TabCtl.Value
Case Me.pagPartsConsumed.PageIndex
If Me.PartsConsumedsbf.Form.RecordSource <> "Equipment - Parts Consumed sbf" Then _
Me.PartsConsumedsbf.Form.RecordSource = "Equipment - Parts Consumed sbf"
....
Now just to ensure that I don't accidentally leave some subform recordsources filled in slowing down the app on startup I check to see if the file the code is running is an MDB (instead of an MDE. The function is below) then display a message telling me I have to remove the recordsource.
If Not tt_IsThisAnMDE Then
If Me.PartsConsumedsbf.Form.RecordSource <> "" Then _
MsgBox "Record source of Equipment - Parts Consumed sbf not empty"
...
End If
Public Function tt_IsThisAnMDE()
On Error GoTo tagError
Dim dbs As Database
Set dbs = CurrentDb
Dim strMDE As String
On Error Resume Next
strMDE = dbs.Properties("MDE")
If Err = 0 And strMDE = "T" Then
' This is an MDE database.
tt_IsThisAnMDE = True
Else
tt_IsThisAnMDE = False
End If
Exit Function
tagError:
Call LogError(Application.CurrentObjectName, "")
Exit Function
End Function
Also in the form unload event I clear the Recourd Source as well.
Private Sub Form_Unload(Cancel As Integer)
On Error GoTo Form_Unload_Error
Me.PartsConsumedsbf.Form.RecordSource = ""
....
BTW I almost always would put each subform on a seperate tab. Also that many tab entries gets visusally unwieldy. When I had a similar question my fellow Access MVPs suggested using a listbox along the left hand side to control which subform is viewable.
Also each combo box and list box will also slightly degrade the performance. So if you have those on a subform then consider similar logic.
In addition to adding recordsets at runtime, I would generally only use one or two tabs and a number of controls to load various subforms into a subform control.
The text for the On Click event of the control might be:
=WhichPage([Form],"lblLocations")
Where WhichPage is a function with the following lines, amongst others:
Function WhichPage(frm, Optional LabelName = "")
<..>
Select Case LabelName
Case "lblLocations"
frm("sfrmAll").SourceObject = "sfrmLocations"
<...>
If necessary, the link child and link master fields can be changed at runtime. The link master field is best set to the name of a control, rather than a field, to avoid errors.
Me.sfrmAll.LinkChildFields = "LocationKey"
Me.sfrmAll.LinkMasterFields = "txtLocationKey"
To expand on Remou's answer...here is a sub I wrote that dynamically loads a form into a subform control. You pass in the name of the form in the call and it will load it into the subform of the Main form. The arguments map to the arguments of Docmd.OpenForm method of Access. If the main form that is hosting the subform control is not open...it just does a regular open of the form. Otherwise it loads it into the subform control. If a where clause was passed in it is used to filter the subform.
Public Sub MyOpenForm(FormName As String, _
Optional View As AcFormView = acNormal, _
Optional FilterName As String = vbNullString, _
Optional WhereCondition As String = vbNullString, _
Optional DataMode As AcFormOpenDataMode, _
Optional WindowMode As AcWindowMode, _
Optional OpenArgs As String)
On Error GoTo PROC_ERR
Dim frm As Form
Dim strNewForm As String
Dim strCurrentForm As String
Dim strNewTable As String
Dim fDoNotFilter As Boolean
Dim strActionText As String
Dim strID As String
If Not IsLoaded("frmMain") Then
DoCmd.OpenForm FormName:=FormName, View:=View, FilterName:=FilterName, WhereCondition:=WhereCondition, DataMode:=DataMode, WindowMode:=WindowMode, OpenArgs:=OpenArgs
Else
strCurrentForm = Forms![frmMain]![sfrMyForm].SourceObject
If strCurrentForm <> FormName Then
Forms![frmMain]![sfrMyForm].SourceObject = vbNullString
Forms![frmMain]![sfrMyForm].SourceObject = FormName
End If
If WhereCondition <> vbNullString Then
Forms![frmMain]![sfrMyForm].Form.Filter = WhereCondition
Forms![frmMain]![sfrMyForm].Form.FilterOn = True
End If
End If
PROC_EXIT:
Exit Sub
PROC_ERR:
MsgBox Err.Description
Resume PROC_EXIT
End Sub