I'm new to MS Access, and I have a form with the fields [name] and [company]. I want to make it required for the user to fill out either of those, but not necessarily both. They can fill out [name], [company] or both, but they can't leave both of them blank.
How do I achieve this?
You can do this in vba:
Create an Eventhandler that runs before the saving of a dataset and tell it to cancel the update if your conditions are not met:
Private Sub Form_BeforeUpdate(Cancel As Integer)
If (isnull(me!name) and isnull(me.company) then
msgBox "Give me more !"
Cancel = True
End If
End Sub
I'd recommend Data Macros in this case (if you're using Access 2010 or later). The advantage over VBA is that Data Macro runs at the engine level, so if you have multiple forms with these fields you don't have to duplicate your VBA to every one of them. This also eliminates the problem with starting entering data and then deleting them.
Go to Table design, click Create Data Macros > Before Change and add If condition like this:
You can use VBA to catch the error number 1 to do some stuff. As Access say, the error numbers are user defined and has no meaning to Access.
Related
I am very new to MS Access, forgive me for this simple question but I am very confused with my current problem.
So I want to run a VBA function after a table receives an update on one of its fields. What I have done is:
Create a Macro named Macro_update under CREATE->Macro, with action RunCode, and its argument is the VBA function I wish to run. The function has no bug.
Select my table, and under Table->After Update, I wrote
IF [Old].[status]=0 And [status]=1 THEN
RunDataMacro
MacroName Macro_update
But after I update my status field in my table nothing happened... I am suspicious of the fact that in step 2 my action is RunDataMacro, but I am actually running a Macro (is there a difference?)... any help is appreciated!
You can use a Data Macro to get it working locally for now. This means that the table will need to be stored in an Access database.
If your web service is not actually using the Access Runtime to interface with the access database container, then the data macros may not fire correctly nor as intended. Your mileage may vary.
If you later migrate your database to a SQL server (MySQL, Microsoft SQL, PostgreSQL) then your data macros will need to be implemented natively on the SQL server as a Trigger.
For now, I'm writing some instructions below to demonstrate how to call a VBA function from a Data Macro locally within a single Access database:
Create the VBA Function This will be the function that you want to call from the data Macro.
Create this in a Module, not in a Form or Class Module.
This has to be a function and cannot be a sub
Code:
Public Function VBAFunction(OldValue As String, NewValue As String) As String
Debug.Print "Old: " & Chr(34) & OldValue & Chr(34);
Debug.Print vbTab;
Debug.Print "New: " & Chr(34) & NewValue & Chr(34)
VBAFunction = "Worked"
End Function
Create the Data Macro (Going to be more descriptive here since people get lost here easy)
Open the Table (i.e. TestTable) in Design View
Find the correct Ribbon
In table design view, there is a contextual ribbon called Design.
On that ribbon, there is an option called Create Data Macros
Click on Create Data Macros and select After Update
The Macro Designer window should now open
Choose SetLocalVar from the Add New Action combo box
A SetLocalVar section appears.
In this section, I see Name and Expression
Set Name to an arbitrary value, such as: MyLocalVar
Set Expression to the following
Be sure to type the = sign, which will result in two equal signs being shown
Expression Text:
=VBAFunction([Old].[FieldName],[FieldName])
Save the Data Macro and Close the Macro Designer.
Save the Table and Close the Table
Test It: Create an Update Query
Next you will need to create an Update Query that performs an update on the Table that houses the Data Macro you just created.
To test this, you can just update a string field in all records to one value.
UPDATE [TestTable] SET [TestText] = "Test"
Run the query
Press Control + G to bring up the Immediate Window. You will notice that the Data Macro fired for every updated record.
I am completely new to VBA. I have been told to add records to a table by using a form and a Save button and given some very basic instructions. While I have achieved this with inline query, I have been told to follow some strict methods like usage of QueryDef/QueryDefs and .Parameters.
So far I am trying a very basic project, just to grasp the concepts, but I am unable to add any record to an empty table. In case the table is not empty(I manually enter a record), whenever I click the Save button for saving newer records, the number of records added are somehow doubling with each instance. For example, when I Save for the 1st time, 1 record is added, 2nd time 2 records of the same type is added, 3rd time 4 are added and so on.
The table(tbl_test) has 2 fields --> ID(primary key), Source(Long Text) and Reg No (Number).
The query(qry_test) is made with the Append feature and I have been told to add expressions which makes the code like this -
INSERT INTO tbl_test ( Source, [Reg No] )
SELECT [strSource] AS Expr1, [lngRegNo] AS Expr2
FROM tbl_test;
The form has 2 fields for Source(txt_Source) and Reg No(txt_RegNo) which have blank Record Sources (Unbound). The Save button has the following Event Procedure -
Private Sub btn_save_Click()
Dim qdf As QueryDef
Set qdf = CurrentDb.QueryDefs("qry_test")
qdf.Parameters("strSource") = Me.txt_Source
qdf.Parameters("lngRegNo") = Me.txt_RegNo
qdf.Execute
End Sub
I have zero knowledge about VBA and would gladly accept ANY help. It would be great if I get any sort of source code that will explain to all the details about saving records from forms and editing them using these querydef, parameter and recordset stuff.
Welcome to StackOverflow!
If you are using a form to collect data for records that are stored in a table, running a saved append query by QueryDefs to do this is, in my opinion, not the best method.
Whilst an append query does add new records to an existing table, I would tend to use an append query where I've got multiple established records that I want to add to an existing table.
If I'm setting up a data entry form that is designed to collect new data for new records 1-at-a-time, an append query doesn't really suit this purpose.
Instead, there are a number of features built in to the design of MS-Access forms to help collect data and save it to a table. This is because forms are very much intended to be set up so that users can interact with records from a table in a controlled, user-friendly way rather than interact directly with the table object itself.
The first and most important feature of a form in this context is probably the form's record source property. When you create a new form, go in to design view for the form and open the form's property sheet (F4 key). In the "Data" tab of the form's property sheet you'll find the record source property:
The record source essentially connects your form with a set of records, whether those be records in a table object, a query object or an sql query string.
In your case it would be better, in my opinion, to bind your tbl_Test table to your form by referring to it in your form's record source:
It will seem like nothing has happened to your form, but if you now open the "Add Existing Fields" panel (alt + F8), you'll notice that the fields associated with your tbl_Test table are available to you:
Drag them to the detail section of your form...
Then put your form in to Form View:
Essentially what you and your users are seeing is the first blank record in your tbl_Test, but displayed on a form instead.
Entering data in these fields on the form...
...will put that data in to the table we specified in the form's record source...
So hopefully you can see that setting the form's record source property to that of your table, is much cleaner than trying to get an append query to collect data from your form and deliver it your table.
At this point you're probably asking a few questions:
When I have filled in the fields for a record on my form, how do I save that record?
More can be said about this, but for brevity, I'd recommend using a command button for running some vba to save the record; similar to what you've done, but utilising the form's Dirty property instead using an append query:
Here's the click event VBA for my save button example:
Private Sub cmdSave_Click()
If Me.Dirty Then
Me.Dirty = False
End If
End Sub
Me.Dirty is a Boolean (True or False) setting for the form; essentially it is automatically set to True when a user changes something on the form. To save those changes, the Me.Dirty setting will have to be set to False.
Me.Dirty is a bit like the swing gate on a sheep pen. When a shepherd puts a sheep (data) in the pen (form) they will open the gate to the pen. The open gate is like the form's Me.Dirty being set to True. To lock the sheep (data) in, the gate needs to be closed, or in the case of forms, the Me.Dirty property needs to be set to False. The VBA above essentially checks to see if the gate was opened and if it was to close it.
How do I move to a new record in the form once I have saved the current one?
Again, I'd give the user a command button to do this and run some VBA on its click event:
Here's the VBA for moving to a new record:
Private Sub cmdNew_Click()
DoCmd.GoToRecord , , acNewRec
End Sub
Summary
There is a lot more to consider than what I've outlined here, such as:
validating data before it is saved
checking other form events (such as close) to ensure data entry is not lost
navigating to an existing record
But hopefully what I've given you here is at least pointing you in a better direction. Best of luck!
Access is optimized for bound forms. Unbound forms are for special cases only.
Should you prefer unbound forms (could be for many reasons), Access isn't worth the trouble, and you should turn to Visual Studio and WinForms.
In either case, browse for a beginner's tutorial.
If you want to use an unbound for or unbound controls you can create the SQL string in the procedure:
Dim mysql as String
mysql = "INSERT INTO tbl_test ( [source], [reg_No]) VALUES (Me.txt_Source, Me.txt_RegNo)
DoCmd.RunSQL mysql
I was wondering if anyone knew how to create a custom message for the save record confirmation, only using macros and not user coded VBA. I've done this for a previous project in VBA but this time I'm trying to only use Access' macro builder. I'm using Access 2010 to build this project.
So far I've got
IF 6=MsgBox("Are you sure you want to delete this customer's information? WARNING, this is unrecoverable."),52 Then
RunMenuCommand DeleteRecord
MessageBox Message "Customer information deleted."
This makes a yes no dialog box first as the confirmation but it then shows the built-in confirmation that Access bundles with the DeleteRecord command.
Thanks,
Jake.
don't use the run menu command. You could for example delete using SQL
currentdb.execute "DELETE * FROM myTable WHERE myTableId=" & idOfRecordToBeDeleted
Looking for a similar solution myself and after much ado, have a solution. Using Access2013, but should be the same logic.
In my case, I wanted a Yes/No message box to add a record on Yes, and reload the form on No. In your case, leaving out the filters and values, it would be simpler... If yes, RunMenuCommand = Delete Record Else StopMacro. You could set up the specifics for No, but Else handles it for me.
Specifics: I have a form with a combo box (macro is tied to the combo box/After Update property) to select and filter the records. If there is a matching record(s), display it(them). If there is no matching record, display a message (no records) and prompt the user to add one (yes) or not (no).
The entire macro.... (no VB)
ApplyFilter
Filter Name (blank)
Where Condition =="[cID]= "& Str(Nz([Screen].[ActiveControl],0))
Control Name (blank)
If IsNull(eID]) Then
If MsgBox("No record",4,"Nothing found")=6 Then
SetProperty
Control Name cID
Property Value
Value =Str(Nz([Screen].[ActiveControl],0))
StopMacro
Else
RunMenuCommand
Command RemoveAllFilters
StopMacro
End If
End If
I would like to know if there is a way to set the parameters in an Access 2007 query using VBA. I am new to using VBA in Access, and I have been tasked with adding a little piece of functionality to an existing app.
The issue I am having is that the same report can be called in two different places in the application. The first being on a command button on a data entry form, the other from a switchboard button. The report itself is based on a parameter query that has requires the user to enter a Supplier ID.
The user would like to not have to enter the Supplier ID on the data entry form (since the form displays the Supplier ID already), but from the switchboard, they would like to be prompted to enter a Supplier ID.
Where I am stuck is how to call the report's query (in the report's open event) and pass the SupplierID from the form as the parameter. I have been trying for a while, and I can't get anything to work correctly. Here is my code so far, but I am obviously stumped.
Private Sub Report_Open(Cancel As Integer)
Dim intSupplierCode As Integer
'Check to see if the data entry form is open
If CurrentProject.AllForms("frmExample").IsLoaded = True Then
'Retrieve the SupplierID from the data entry form
intSupplierCode = Forms![frmExample]![SupplierID]
'Call the parameter query passing the SupplierID????
DoCmd.OpenQuery "qryParams"
Else
'Execute the parameter query as normal
DoCmd.OpenQuery "qryParams"?????
End If
End Sub
I've tried Me.SupplierID = intSupplierCode, and although it compiles, it bombs when I run it. And here is my SQL code for the parameter query:
PARAMETERS [Enter Supplier] Long;
SELECT Suppliers.SupplierID, Suppliers.CompanyName, Suppliers.ContactName, Suppliers.ContactTitle
FROM Suppliers
WHERE (((Suppliers.SupplierID)=[Enter Supplier]));
I know there are ways around this problem (and probably an easy way as well) but like I said, my lack of experience using Access and VBA makes things difficult. If any of you could help, that would be great!
The suggestion being made here is to 100% REMOVE the parameter from the query. This not only solves your problem, but then means you can use the query for code, other forms and not have your whole design fall apart because one stupid form is not open (hence the VERY reason for your question).
So, remove the parameters from the query. This also means that your report will now not need some form that already opened. And again, if some silly form is not opened, why should your report fail to work?
So, remove the parameter. Now, in your form that opens the report, it can pass the filter, and more in point use what is a called a "where" clause. This "where" clause is designed in MS-access to solve the problem of having to know ahead of time what kind of parameters and filters you need. It occurs at runtime, and thus MANY DIFFERENT forms can call and open that report.
Now in the form that calls and opens the form, you go:
Docmd.OpenReport "rptSuppliers",acViewPreview, , _
"SupplierCode = " & me.SupplierCode
So, in the above, the parameter is created on the fly. The great advantage is tomorrow you can have another form open the same report and perhaps filter by region.
In the case of NO where clause being passed and a user simply opening the form, then no filters will be used and no prompts will occur and all records will show. This is probably your best approach.
However if for some strange reason you still deem it REALLY necessary to have some report prompt when one silly form just happens to not be opened, then place the following code in the forms on-open event.
If CurrentProject.AllForms("form1").IsLoaded = False Then
Me.Filter = "SupplierID = " & InputBox("Enter Supplier ID")
Me.FilterOn = True
End
However, I would really make efforts to avoid hard coding some silly form name in the reports open event. Not only does this mean your hard coding dependencies of some silly form that is now attached to a report, but if you later on copy that report, or even copy the original form (or even rename any of these objects), then you have to go into the application and hunt about and now find the places you as a developer introduced dependences. This approach can substantially increase the maintenance costs of an application and thus should be advoied.
So, the suggestion here is to dump the parameter query. Simply provide a form or some prompt system to launch the reports. Those forms should prompt the user for the information you wish to filter. Or as in your case the bound form and it current record provides that information. The beauty of this system is now there is no depdancy from the report.
Any form, or even any code down the road is free to pass a pramaeter, and it will not be limited to SupplierID, but can be any type of filter or parameter you wish.
Keep in mind that perhaps the user might not want that form to be open and perhaps they don't want the prompt. With your design and question the user will be forced to enter a parameter value even when launching the report without any forms open and not desiring to be prompted to allow them to view all reocrds in that report.
As I outlined in a recent post, I tend never to hardwire any parameters or form control references into the recordsources of reports or forms. Instead, I set them at runtime. The simplest way is by passing the WhereCondition property in the DoCmd.OpenForm/DoCmd.OpenReport:
DoCmd.OpenReport "MyReport", , , "[SupplierID]=" & Me!SupplierID
That assumes you're running it from a form that has the relevant SupplierID already present in its recordsource (i.e., you're on a record with that SupplierID).
More complicated is to use the OnOpen event of the report to set the reports's recordsource. That's what I outlined in the cited post above. But that example hardwires the choice to a selection form, whereas you might want to instead offer different sets of choices depending on context. There are two ways to handle that:
if A2003 and later, pass an OpenArg (the last parameter of the DoCmd.OpenReport) to tell the OnOpen event what to do to collect the information on what to filter to.
use an outside structure like a standalone class module to store criteria that the OnOpen event will read and act upon accordingly.
I suspect that the WhereCondition in the DoCmd.OpenReport is your easiest solution, but if you want details on the other two, just ask.
Lets say you are designing a sales report in microsoft access. You have 2 parameters: Startdate and EndDate.
I can think of 3 ways to prompt the end user for these parameters when the report is run.
Create a form with 2 Text Boxes and a button. The Button runs the report, and the report refers back to the form by name to get the start date and end date.
Create a form with 2 text boxes and a button. The button runs the report but sets the appropriate filter via the docmd.openreport command.
Base the report on a Query and define query parameters. Access will automatically prompt for those parameters one by one.
Which is the best way to go?
Which is best depends on a number of factors. First off, if you want to run the report without parameters, you don't want to define them in the recordsource of the report. This is also the problem with your first suggestion, which would tie the report to the form (from Access/Jet's point of view, there is little difference between a PARAMETER declared in the SQL of the recordsource and a form control reference; indeed, if you're doing it right, any time you use a form control reference, you will define it as a parameter!).
Of the three, the most flexible is your second suggestion, which means the report can be run without needing to open the form and without needing to supply the parameters at runtime.
You've left out one possibility that's somewhat in between the two, and that's to use the Form's OnOpen event to set the recordsource at runtime. To do that, you'd open the form where you're collecting your dates using the acDialog argument, hide the form after filling it out, and then write the recordsource on the fly. Something like this:
Private Sub Report_Open(Cancel As Integer)
Dim dteStart as Date
Dim dteEnd As Date
DoCmd.OpenForm "dlgGetDates", , , , , acDialog
If IsLoaded("dlgGetDates") Then
With Forms!dlgGetDates
dteStart = !StartDate
dteEnd = !EndDate
End With
Me.Recordsource = "SELECT * FROM MyTable WHERE DateField Between #" _
& dteStart & "# AND #" & dteEnd & "#;"
DoCmd.Close acForm, "dlgGetDates"
End If
End Sub
[Edit: IsLoaded() is a function provided by MS. It's so basic to all my Access coding I forget it's not a built-in function. The code:]
Function IsLoaded(ByVal strFormName As String) As Boolean
' Returns True if the specified form is open in Form view or Datasheet view.
Const conObjStateClosed = 0
Const conDesignView = 0
If SysCmd(acSysCmdGetObjectState, acForm, strFormName) <> conObjStateClosed Then
If Forms(strFormName).CurrentView <> conDesignView Then
IsLoaded = True
End If
End If
End Function
Now, there are certain advantages to this approach:
you can pre-fill the dates such that the user would have to click only OK.
you can re-use the form in multiple locations to collect date values for multiple forms, since the form doesn't care what report it's being used in.
However, there is no conditional way to choose whether to open the form or not.
Because of that, I often use a class module to store filtering criteria for reports, and then check if the relevant class module instance has filter values set at the time the OnOpen event fires. If the criteria are set, then I write the SQL for the recordsource on the fly, if they aren't set, I just use the default recordsource (and any filter passed in the OpenReport argument). In this setup, you would run the report only after you've collected the criteria and set up the class module instance. The advantage of this approach is that the report can be run in either of the two contexts, and none of the parts need to know anything about each other -- the report simply needs to understand the class module's interface.
I used to use the Docmd.Openreport Where clause, your option 2. But I've switched to using report filters in the reports Open event, your option 1, as it supports creating PDF files which the Where clause does not. See Microsoft Access Report Printing Criteria Selection Form at my website.
As far as the dates go in SQL strings I use the Format statement as at Return Dates in US #mm/dd/yyyy# format
Also your option three can be handled by putting in the following example in the criteria in the reports record source SQL query. Forms!Form_Name!Control_Name. This is generally simpler for folks who don't want to get into VBA.